Compare commits

..

1 Commits

Author SHA1 Message Date
ducoterra
6610c2896b add visitor model and api 2020-04-26 12:05:03 -04:00
17 changed files with 139 additions and 283 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,3 @@
venv/ venv/
__pycache__/ __pycache__/
db/ db/
staticfiles/

6
.vscode/launch.json vendored
View File

@@ -12,8 +12,7 @@
"args": [ "args": [
"test", "test",
], ],
"django": true, "django": true
"preLaunchTask": "Migrate"
}, },
{ {
"name": "Run Server", "name": "Run Server",
@@ -24,8 +23,7 @@
"runserver", "runserver",
"--noreload" "--noreload"
], ],
"django": true, "django": true
"preLaunchTask": "Migrate"
} }
] ]
} }

24
.vscode/tasks.json vendored
View File

@@ -1,24 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Collect Static",
"command": "venv/bin/python manage.py collectstatic --no-input",
"type": "shell",
"presentation": {
"reveal": "always"
},
"group": "build"
},
{
"dependsOn": "Collect Static",
"label": "Migrate",
"command": "venv/bin/python manage.py migrate",
"type": "shell",
"presentation": {
"reveal": "always"
},
"group": "build"
}
]
}

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.0.5 on 2020-04-26 15:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Visitor',
fields=[
('name', models.CharField(max_length=255, primary_key=True, serialize=False)),
('clicked', models.IntegerField(blank=True, default=0)),
('first_pressed', models.DateTimeField(auto_now_add=True)),
('last_pressed', models.DateTimeField(auto_now=True)),
],
),
migrations.DeleteModel(
name='Snippet',
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.0.5 on 2020-04-26 15:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0002_auto_20200426_1517'),
]
operations = [
migrations.AlterField(
model_name='visitor',
name='first_pressed',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='visitor',
name='last_pressed',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@@ -1,19 +1,11 @@
from django.db import models from django.db import models
from pygments.lexers import get_all_lexers from datetime import datetime
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]] class Visitor(models.Model):
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) name = models.CharField(
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()]) primary_key=True,
max_length = 255
)
class Snippet(models.Model): clicked = models.IntegerField(default = 0, blank=True)
created = models.DateTimeField(auto_now_add=True) first_pressed = models.DateTimeField(blank=True, null=True)
title = models.CharField(max_length=100, blank=True, default='') last_pressed = models.DateTimeField(blank=True, null=True)
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ['created']

View File

@@ -1,29 +1,23 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES from .models import *
class SnippetSerializer(serializers.Serializer): class VisitorSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True) name = serializers.CharField(required=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100) clicked = serializers.IntegerField(read_only=True)
code = serializers.CharField(style={'base_template': 'textarea.html'}) first_pressed = serializers.DateTimeField(read_only=True)
linenos = serializers.BooleanField(required=False) last_pressed = serializers.DateTimeField(read_only=True)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data): def create(self, validated_data):
""" """
Create and return a new `Snippet` instance, given the validated data. Create and return a new `Visitor` instance, given the validated data.
""" """
return Snippet.objects.create(**validated_data) return Visitor.objects.create(**validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
""" """
Update and return an existing `Snippet` instance, given the validated data. Update and return an existing `Snippet` instance, given the validated data.
""" """
instance.title = validated_data.get('title', instance.title) instance.name = validated_data.get('name', instance.name)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save() instance.save()
return instance return instance

View File

@@ -1,42 +1,53 @@
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from django.db.utils import IntegrityError
from .views import SnippetList, SnippetDetail from .views import *
class SimpleTest(TestCase): class SimpleTest(TestCase):
def setUp(self): def setUp(self):
# Every test needs access to the request factory. # Every test needs access to the request factory.
self.factory = RequestFactory() self.factory = RequestFactory()
self.user = User.objects.create_user( # self.user = User.objects.create_user(
username='testuser', email='test@test.test', password='testpass') # username='testuser', email='test@test.test', password='testpass')
def test_snippets(self): def test_list_visitors(self):
# Create an instance of a GET request. # Create an instance of a GET request.
request = self.factory.get('/snippets') request = self.factory.get('/visitors')
# Recall that middleware are not supported. You can simulate a # Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually. # logged-in user by setting request.user manually.
request.user = self.user # request.user = self.user
# Or you can simulate an anonymous user by setting request.user to # Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance. # an AnonymousUser instance.
# request.user = AnonymousUser() # request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details # Test my_view() as if it were deployed at /customer/details
response = SnippetList.as_view()(request) response = VisitorList.as_view()(request)
# Use this syntax for class-based views. # Use this syntax for class-based views.
# response = MyView.as_view()(request) # response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
request = self.factory.post('/snippets', data={ def test_add_visitor(self):
'title': 'test1', request = self.factory.post('/visitors', data={
'code': '() => {console.log("hello")};', 'name': 'test',
'lineos': False,
'language': 'js',
'style': 'abap'
}, },
content_type='application/json' content_type='application/json'
) )
response = SnippetList.as_view()(request) response = VisitorList.as_view()(request)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
vis = Visitor.objects.get(name='test')
self.assertEqual(vis.clicked, 0)
self.assertIsNone(vis.first_pressed)
self.assertIsNone(vis.last_pressed)
with self.assertRaises(IntegrityError):
request = self.factory.post('/visitors', data={
'name': 'test',
},
content_type='application/json'
)
response = VisitorList.as_view()(request)

View File

@@ -3,8 +3,8 @@ from rest_framework.urlpatterns import format_suffix_patterns
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('snippets/', views.SnippetList.as_view()), path('visitor/', views.VisitorList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()), path('visitor/<str:pk>/', views.VisitorDetail.as_view()),
] ]
urlpatterns = format_suffix_patterns(urlpatterns) urlpatterns = format_suffix_patterns(urlpatterns)

View File

@@ -1,15 +1,15 @@
from .models import Snippet from .models import *
from .serializers import SnippetSerializer from .serializers import *
from rest_framework import generics from rest_framework import generics
class SnippetList(generics.ListCreateAPIView): class VisitorList(generics.ListCreateAPIView):
# Add comments here # Add comments here
queryset = Snippet.objects.all() queryset = Visitor.objects.all()
serializer_class = SnippetSerializer serializer_class = VisitorSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): class VisitorDetail(generics.RetrieveUpdateDestroyAPIView):
# Add comments here # Add comments here
queryset = Snippet.objects.all() queryset = Visitor.objects.all()
serializer_class = SnippetSerializer serializer_class = VisitorSerializer

View File

@@ -18,7 +18,7 @@ from django.urls import path, include
from django.http import JsonResponse from django.http import JsonResponse
urlpatterns = [ urlpatterns = [
# path('api/', include('api.urls')), path('api/', include('api.urls')),
path('', include('ui.urls')), path('', include('ui.urls')),
# path('admin/', admin.site.urls), # path('admin/', admin.site.urls),
] ]

View File

@@ -1,41 +0,0 @@
.achievement {
position: absolute;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: -1;
}
.achievement-animate {
animation-name: moveup;
animation-duration: 2s;
animation-fill-mode: forwards;
}
.achievment-column {
height: 100%;
overflow: auto;
text-align: center;
}
.achievement-text {
font-size: 4rem;
}
@media only screen and (max-width: 768px) {
.achievment-column {
height: 50%;
}
}
@keyframes moveup {
from {bottom: 0px;}
to {bottom: 200px; color: white;}
}
@keyframes fadeout {
from {}
to {color: transparent;}
}

View File

@@ -1,32 +1,18 @@
html, body { html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: fixed;
overflow: hidden;
} }
.section, .container { .section, .container {
height: 100%; height: 100%;
} }
.columns { .container {
height: 100%;
}
.button-column {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
text-align: center; text-align: center;
height: 100%;
}
@media only screen and (max-width: 768px) {
.button-column {
height: 50%;
}
}
.button-container {
position: absolute;
} }

View File

@@ -1,25 +1,6 @@
const csrftoken = getCookie('csrftoken'); const csrftoken = getCookie('csrftoken');
const button = document.getElementById("BUTTON"); const button = document.getElementById("BUTTON");
const count = document.getElementById("COUNT"); const count = document.getElementById("COUNT");
const button_container = document.getElementById("button-container");
const achievement = document.getElementById("achievement");
const achievement_list = document.getElementById("achievement-list");
const achievement_column = document.getElementById("achievement-column");
function add_achievement(text) {
if (text != undefined) {
achievement.querySelector(".achievement-text").innerText = text;
achievement.classList.remove("achievement-animate");
void achievement.offsetWidth;
achievement.classList.add("achievement-animate");
var elem = document.createElement("div");
elem.innerText = text;
achievement_list.appendChild(elem);
achievement_column.scrollTo(0, achievement_list.scrollHeight);
}
}
// when button is clicked submit an empty post request // when button is clicked submit an empty post request
button.addEventListener("click", event => { button.addEventListener("click", event => {
@@ -37,7 +18,6 @@ button.addEventListener("click", event => {
}) })
.then((data) => { .then((data) => {
count.innerText = data.pressed; count.innerText = data.pressed;
add_achievement(data.achievement);
}).finally(() => { }).finally(() => {
button.disabled = false; button.disabled = false;
button.classList.remove("is-loading"); button.classList.remove("is-loading");

View File

@@ -4,7 +4,6 @@
{% block css %} {% block css %}
<link rel="stylesheet" href="{% static 'ui/button.css' %}"> <link rel="stylesheet" href="{% static 'ui/button.css' %}">
<link rel="stylesheet" href="{% static 'ui/achievement.css' %}">
{% endblock %} {% endblock %}
{% block js %} {% block js %}
@@ -14,33 +13,16 @@
{% block body %} {% block body %}
<section class="section"> <section class="section">
<div class="columns"> <div class="container">
<div class="column"> <div>
<h1 class="title">
The Button
</h1>
<button class="button is-primary" id="BUTTON">Press</button>
</div> </div>
<div class="button-column column"> <div><br></div>
<div> <div>
<h1 class="title"> <h1 class="title" id="COUNT">{{ pressed }}</h1>
The Button
</h1>
<button class="button is-primary" id="BUTTON">Press</button>
</div>
<div><br></div>
<div>
<h1 class="title" id="COUNT">{{ pressed }}</h1>
</div>
<div id="achievement" class="achievement">
<div>
<div class="achievement-text"></div>
</div>
</div>
</div>
<div class="column achievment-column" id = "achievement-column">
<div class="achievements-list" id="achievement-list">
<h1 class="title">Achievements</h1>
{% for key,value in achievement.items %}
<div>{{ value }}</div>
{% endfor %}
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,5 +1,5 @@
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
from django.test import RequestFactory, TestCase, Client from django.test import RequestFactory, TestCase
from .views import button from .views import button
@@ -11,31 +11,24 @@ class SimpleTest(TestCase):
username='testuser', email='test@test.test', password='testpass') username='testuser', email='test@test.test', password='testpass')
def test_button(self): def test_button(self):
# Test initial load # Test button page returns 200
c = Client() request = self.factory.get('/button')
response = c.get('/button') request.session = self.client.session
self.assertEqual(response.status_code, 301) response = button(request)
response = c.get('/button/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context.get("achievement"), {})
# Test button post returns 200 and increments button to 1
request = self.factory.post(
'/button',
data={},
content_type='application/json'
)
request.session = self.client.session
response = button(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(request.session.get('pressed'), 1)
# Test first achievement # Test second click increments button to 2
response = c.post('/button/', {}) response = button(request)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json().get("pressed"), 1) self.assertEqual(request.session.get('pressed'), 2)
self.assertEqual(response.json().get("achievement"), "Clicked!")
self.assertEqual(c.session.get('pressed'), 1)
# Test second achievement
response = c.post('/button/', {})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json().get("pressed"), 2)
self.assertEqual(response.json().get("achievement"), "Clicked Twice!")
self.assertEqual(c.session.get('pressed'), 2)
# Test no achievement
response = c.post('/button/', {})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json().get("pressed"), 3)
self.assertEqual(response.json().get("achievement"), None)
self.assertEqual(c.session.get('pressed'), 3)

View File

@@ -1,61 +1,8 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import JsonResponse from django.http import JsonResponse
achievements = {
1: "Clicked!",
2: "Clicked Twice!",
4: "2^2",
8: "2^3",
16: "2^4",
24: "I'm that old",
32: "2^5",
64: "2^6",
69: "Nice",
100: "one hundred",
128: "2^7",
200: "two hundred",
250: "quarter thousand",
256: "2^8",
300: "three hundred",
400: "four hundred",
420: "Blaze it",
500: "half thousand",
512: "2^9",
600: "six hundred",
700: "seven hundred",
800: "eight hundred",
900: "nine hundred",
1000: "full thousand",
1024: "2^10",
1776: "America",
1914: "Some War here",
1938: "Some more war here",
1950: "Lots of war in here",
2000: "Computers die",
2008: "Houses die",
2019: "People die",
2048: "2048!",
2500: "Keep going!",
3000: "three thousand",
4000: "four thousand",
4096: "2^11",
5000: "halfway to ten thousand",
10001: "ten thousand one",
100000: "one hundred thousand",
1000000: "one million?",
10000000: "ten millions???",
100000000: "one hundo billion",
1000000000: "JK this is actually a billion though",
10000000000: "I'm not going to create another achievement",
100000000000: "one hundred billion",
1000000000000: "It's physically impossible to click this high"
}
def button(request): def button(request):
PRESSED = 'pressed' PRESSED = 'pressed'
ACHIEVE = 'achievement'
try: try:
request.session[PRESSED] request.session[PRESSED]
except KeyError: except KeyError:
@@ -63,14 +10,5 @@ def button(request):
if request.method == "POST": if request.method == "POST":
request.session[PRESSED] += 1 request.session[PRESSED] += 1
response = { return JsonResponse({PRESSED: request.session[PRESSED]})
PRESSED: request.session[PRESSED], return render(request, "ui/button.html", {PRESSED: request.session[PRESSED]})
ACHIEVE: achievements.get(request.session[PRESSED])
}
return JsonResponse(response)
pressed = request.session[PRESSED]
response = {PRESSED: pressed}
achieved = {k:v for k,v in achievements.items() if k <= pressed}
response.update({ACHIEVE: achieved})
return render(request, "ui/button.html", response)