Added code for user management

Added templates
Form rendering is fucked up
This commit is contained in:
Sebastian 2017-04-28 22:01:46 +02:00
parent c5d690d7f9
commit daa4be5b14
44 changed files with 14470 additions and 99 deletions

BIN
design/background.xcf Normal file

Binary file not shown.

BIN
design/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
design/logo.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

1
design/mockup/bulma Submodule

@ -0,0 +1 @@
Subproject commit a0244ea92401879f1a2435616aea98fc4404bdf8

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
@charset "utf-8"
$primary: #e60000
@import "bulma/bulma"
.rom-overview
padding: 1vw
background-image: url("background.jpg")
background-size: cover
background-repeat: no-repeat
background-attachment: fixed
background-position: center
.categorie
margin: 0.5vw

140
design/mockup/index.html Normal file
View File

@ -0,0 +1,140 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Gulashromstore Mockup</title>
<link rel="stylesheet" type="text/css" href="gulash_bulma.css">
<link rel="stylesheet" type="text/css" href="fontawsome/css/font-awesome.min.css">
<script src="jquery-3.2.1.slim.min.js"></script>
</head>
<body>
<nav class="nav has-shadow">
<div class="container">
<div class="nav-left">
<a class="nav-item">
<img src="logo.png" />
</a>
<a class="nav-item is-tab is-hidden-mobile is-active">Roms</a>
<a class="nav-item is-tab is-hidden-mobile">Upload Rom</a>
</div>
<span class="nav-toggle">
<span></span>
<span></span>
<span></span>
</span>
<div class="nav-right nav-menu">
<a class="nav-item is-tab is-hidden-tablet is-active">Roms</a>
<a class="nav-item is-tab is-hidden-tablet">Rom Hochladen</a>
<a class="nav-item is-tab">Registrieren</a>
<a class="nav-item is-tab">
Einstellungen
</a>
<a class="nav-item is-tab">Login</a>
</div>
</div>
</nav>
<div class="rom-overview">
<div class="columns">
<div class="column">
<div class="box">
<strong>Kategorie:</strong>
<a hre=""><span class="tag is-medium categorie">Gulasch</span></a>
<a hre=""><span class="tag is-primary is-medium categorie">Blinkenlight</span></a>
<a hre=""><span class="tag is-medium categorie">1337 Haxxoring</span></a>
<a hre=""><span class="tag is-medium categorie">Random</span></a>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 1</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 2</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 3</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 4</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 5</h1>
</a>
</div>
</div>
<div class="columns">
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 6</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 7</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 8</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 9</h1>
</a>
</div>
<div class="column">
<a class="box has-text-centered">
<img src="http://placehold.it/400x400" />
<h1 class="title is-4">Awsøme R0m 10</h1>
</a>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
<strong>Gulasch R0MSTØRE</strong> by Sebastian 2017
</div>
</div>
</footer>
<script type="text/javascript">
$('.nav-toggle').on('click', function(event) {
$('.nav-toggle').toggleClass('is-active');
$('.nav-menu').toggleClass('is-active');
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

BIN
design/mockup/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -58,7 +58,7 @@ ROOT_URLCONF = 'gulashromstore.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -123,6 +123,10 @@ USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

View File

@ -6,6 +6,7 @@ from gulashromstore.settings import MEDIA_URL, MEDIA_ROOT
urlpatterns = [
url(r'^roms/', include('roms.urls')),
url(r'^users/', include('users.urls')),
url(r'^admin/', admin.site.urls),
]

View File

@ -2,21 +2,7 @@ from django.contrib import admin
from taggit_helpers.admin import TaggitListFilter, TaggitTabularInline
from roms.models import Rom, RomFile
from roms.forms import RomFileForm, RomFileInlineFormSet
from gulashromstore.settings import SLOT_COUNT
class RomFileInline(admin.TabularInline):
model = RomFile
form = RomFileForm
formset = RomFileInlineFormSet
fields = ('slot', 'binary')
can_delete = False
max_num = SLOT_COUNT
min_num = SLOT_COUNT
from roms.models import Rom
class RomAdmin(admin.ModelAdmin):
@ -24,14 +10,10 @@ class RomAdmin(admin.ModelAdmin):
list_filter = [TaggitListFilter]
actions = ['mark_approved', 'mark_disapproved']
fieldsets = ((None, {'fields' : ('name', 'description', 'cover', 'approved')}),)
inlines = [
RomFileInline,
TaggitTabularInline
]
def tag_list(self, obj):
return u", ".join(obj.tag_list())

View File

@ -1,42 +0,0 @@
from django.forms import Field, Widget, ModelForm
from django.forms.models import BaseInlineFormSet
from roms.models import RomFile
class ReadOnlyWidget(Widget):
def render(self, name, value, attrs):
final_attrs = self.build_attrs(attrs, name=name)
if hasattr(self, 'initial'):
value = self.initial
return "%s" % (value or '')
def _has_changed(self, initial, data):
return False
class ReadOnlyField(Field):
widget = ReadOnlyWidget
def __init__(self, widget=None, label=None, initial=None, help_text=None):
super(ReadOnlyField, self).__init__(self, label=label, initial=initial,
help_text=help_text, widget=widget)
def clean(self, value, initial):
self.widget.initial = initial
return initial
class RomFileForm(ModelForm):
slot = ReadOnlyField()
class Meta:
model = RomFile
fields = ['binary']
class RomFileInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(RomFileInlineFormSet, self).__init__(*args, **kwargs)
# Check that the data doesn't already exist
if not kwargs['instance'].romfile_set.all():
self.initial = [{'slot' : i} for i in range(1, SLOT_COUNT+1)]
self.extra = SLOT_COUNT

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-18 17:30
# Generated by Django 1.10.6 on 2017-04-28 17:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import roms.models
import taggit.managers
@ -22,22 +22,10 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='name')),
('description', models.TextField(verbose_name='description')),
('cover', models.ImageField(upload_to='', verbose_name='cover image')),
('cover', models.ImageField(upload_to=roms.models.upload_cover_to, verbose_name='cover image')),
('binary', models.FileField(upload_to=roms.models.upload_binary_to, verbose_name='binary')),
('approved', models.BooleanField(verbose_name='approved')),
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
],
),
migrations.CreateModel(
name='RomFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slot', models.IntegerField(choices=[(1, 'Slot 1'), (2, 'Slot 2'), (3, 'Slot 3'), (4, 'Slot 4'), (5, 'Slot 5'), (6, 'Slot 6')], verbose_name='slot')),
('binary', models.FileField(upload_to='', verbose_name='binary')),
('rom', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='roms.Rom', verbose_name='rom')),
],
),
migrations.AlterUniqueTogether(
name='romfile',
unique_together=set([('rom', 'slot')]),
),
]

View File

@ -6,8 +6,6 @@ from django.core.exceptions import ValidationError
from taggit.managers import TaggableManager
from gulashromstore.settings import SLOT_COUNT
def upload_cover_to(instance, filename):
_, ext = os.path.splitext(filename)
return "covers/%s.%s" % (uuid.uuid4(), ext)
@ -20,10 +18,10 @@ class Rom(models.Model):
name = models.CharField("name", max_length = 128)
description = models.TextField("description")
cover = models.ImageField("cover image", upload_to=upload_cover_to)
binary = models.FileField("binary", upload_to=upload_binary_to)
approved = models.BooleanField("approved")
tags = TaggableManager()
def tag_list(self):
return [t.name for t in self.tags.all()]
@ -33,24 +31,11 @@ class Rom(models.Model):
'name' : self.name,
'description' : self.description,
'tags' : self.tag_list(),
'roms' : {romfile.slot : romfile.binary.url for romfile in self.romfile_set.all()}
'cover' : self.cover,
'binary' : self.binary
}
return json
def __str__(self):
return "Rom %s" % self.name
class RomFile(models.Model):
SLOT_CHOICES = [(i, "Slot %d" % i) for i in range(1, SLOT_COUNT+1)]
rom = models.ForeignKey(Rom, verbose_name = "rom")
slot = models.IntegerField("slot", choices=SLOT_CHOICES)
binary = models.FileField("binary", upload_to=upload_binary_to)
class Meta:
unique_together = ('rom', 'slot')
def __str__(self):
return "RomFile %s %d" % (self.rom.name, self.slot)

4284
static/css/gulash_bulma.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

BIN
static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

4
static/js/jquery-3.2.1.slim.min.js vendored Normal file

File diff suppressed because one or more lines are too long

62
templates/base.html Normal file
View File

@ -0,0 +1,62 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Gulashromstore Mockup</title>
<link rel="stylesheet" type="text/css" href="{% static "css/gulash_bulma.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "fontawsome/css/font-awesome.min.css" %}">
<script src="{% static "jquery-3.2.1.slim.min.js" %}"></script>
</head>
<body>
<nav class="nav has-shadow">
<div class="container">
<div class="nav-left">
<a class="nav-item">
<img src="{% static "images/logo.png" %}"/>
</a>
<a class="nav-item is-tab is-hidden-mobile is-active">Roms</a>
<a class="nav-item is-tab is-hidden-mobile">Upload Rom</a>
</div>
<span class="nav-toggle">
<span></span>
<span></span>
<span></span>
</span>
<div class="nav-right nav-menu">
<a class="nav-item is-tab is-hidden-tablet is-active">Roms</a>
<a class="nav-item is-tab is-hidden-tablet">Rom Hochladen</a>
<a class="nav-item is-tab">Registrieren</a>
<a class="nav-item is-tab">
Einstellungen
</a>
<a class="nav-item is-tab">Login</a>
</div>
</div>
</nav>
{% block content %}
{% endblock %}
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
<strong>Gulasch R0MSTØRE</strong> by Sebastian 2017
</div>
</div>
</footer>
<script type="text/javascript">
$('.nav-toggle').on('click', function(event) {
$('.nav-toggle').toggleClass('is-active');
$('.nav-menu').toggleClass('is-active');
});
</script>
</body>
</html>

20
templates/form.html Normal file
View File

@ -0,0 +1,20 @@
<form {% if form_enctype %}enctype="{{form_enctype}}"{% endif %} method="post" class="pure-form pure-form-aligned">
{% csrf_token %}
{% for field in form %}
<div class="field is-danger">
<label class="label" for="{{field.name}}">{{field.label}}</label>
<p class="control">
{{ field }}
</p>
<p class="help is-danger">
{{ field.errors.as_text}}
</p>
</div>
{% endfor %}
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">{{button_text}}</button>
</p>
</fieldset>
</form>

101
users/forms.py Normal file
View File

@ -0,0 +1,101 @@
from django import forms
from django.forms import ModelForm
from users.models import User
class UserCreateForm(ModelForm):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'input'}))
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput(attrs={'class': 'input'}))
class Meta:
model = User
fields = ['username', 'email', 'twitter', 'github']
widgets = {
'username' : forms.TextInput(attrs={'class': 'input'}),
'email' : forms.TextInput(attrs={'class': 'input'}),
'twitter' : forms.TextInput(attrs={'class': 'input'}),
'github' : forms.TextInput(attrs={'class': 'input'}),
}
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password1
def clean_email(self):
#Make sure Email is unique
email = self.cleaned_data.get("email")
if User.objects.filter(email = email):
raise forms.ValidationError("Email already in use.")
return email
def save(self, commit=True):
user = super(UserCreateForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.is_active = False
if commit:
user.save()
return user
class UserUpdateForm(ModelForm):
new_password1 = forms.CharField(label='New Password',
widget=forms.PasswordInput,
required=False)
new_password2 = forms.CharField(label='New Password confirmation',
widget=forms.PasswordInput,
required=False)
current_password = forms.CharField(label='Current Password', widget=forms.PasswordInput)
class Meta:
model = User
fields = ['email', 'twitter', 'github']
def clean_current_password(self):
current_password = self.cleaned_data.get("current_password")
if not self.instance.check_password(current_password):
raise forms.ValidationError("Password incorrect")
return current_password
def clean_new_password2(self):
password1 = self.cleaned_data.get("new_password1")
password2 = self.cleaned_data.get("new_password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password1
def clean_email(self):
#Make sure Email is still unique
email = self.cleaned_data.get("email")
if User.objects.filter(email = email).exclude(id=self.instance.id):
raise forms.ValidationError("Email already in use.")
return email
def save(self, commit=True):
user = super(UserUpdateForm, self).save(commit=False)
password = self.cleaned_data.get("new_password1")
if password:
user.set_password(password)
if commit:
user.save()
return user

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-04-28 17:43
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='twitter',
field=models.CharField(blank=True, max_length=128, verbose_name='twitter handle'),
),
migrations.AlterField(
model_name='user',
name='github',
field=models.CharField(blank=True, max_length=128, verbose_name='github handle'),
),
]

View File

@ -2,4 +2,5 @@ from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
github = models.CharField('github handle', max_length=128)
github = models.CharField('github handle', max_length=128, blank=True)
twitter = models.CharField('twitter handle', max_length=128, blank=True)

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %} Sign up{% endblock %}
{% block content %}
<div class="rom-overview">
<div class="columns">
<div class="column">
<div class="box">
<h1 class="title is-1">Registrieren</h1>
{% if user.is_authenticated %}
You are alerady logged in, why in the world would you want to sign up ?
{% else %}
{% include 'form.html' with button_text='sign up' %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

30
users/urls.py Normal file
View File

@ -0,0 +1,30 @@
from django.conf.urls import include, url
from django.core.urlresolvers import reverse_lazy
from django.contrib.auth.views import password_reset, password_reset_confirm, password_reset_done, login, logout
from users.views import *
urlpatterns = [
url(r'^signup/$', UserCreateView.as_view(), name='signup'),
url(r'^confirm/(?P<user_id>\d+)/$', SendConfirmationView.as_view(), name='send_confirmation'),
url(r'^confirm/(?P<user_id>\d+)/(?P<token>.+)/$', CheckConfirmationView.as_view(), name='check_confirmation'),
url(r'^update/(?P<user_id>\d+)/$', UserUpdateView.as_view(), name='user_update'),
url(r'^password/reset/$', password_reset, {'template_name' : 'users/password_reset.html',
'post_reset_redirect' : reverse_lazy('password_reset_sent')},
name='password_reset'),
url(r'^password/reset/(?P<uidb36>[0-9A-Za-z]+)/(?P<token>.+)/$', password_reset_confirm,
{'template_name' : 'users/password_reset_confirm.html',
'post_reset_redirect' : reverse_lazy('login')},
name='password_reset_confirm'),
url(r'^password/reset/sent/$', password_reset_done, { 'template_name' : 'users/password_reset_sent.html',},
name='password_reset_sent'),
url(r'^login/$', login, {'template_name' : 'users/login.html'}, name='login'),
url(r'^logout/$', logout ,{'next_page' : reverse_lazy('login')}, name='logout'),
]

View File

@ -1,3 +1,99 @@
from django.shortcuts import render
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.base import TemplateView
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.contrib.auth.tokens import default_token_generator
from django.template import loader
from django.utils.http import int_to_base36
from django.http import Http404
from django.core.exceptions import PermissionDenied
# Create your views here.
from users.forms import UserCreateForm, UserUpdateForm
class UserCreateView(CreateView):
model = User
form_class = UserCreateForm
template_name = 'users/signup.html'
def get_success_url(self):
return reverse('send_confirmation', kwargs={'user_id' : self.object.pk})
class UserUpdateView(UpdateView):
model = User
form_class = UserUpdateForm
template_name = 'users/user_update.html'
pk_url_kwarg = 'user_id'
def get_queryset(self):
return self.model.objects.filter(id=self.request.user.id)
def get_success_url(self):
return reverse('geometry_create')
class SendConfirmationView(TemplateView):
template_name = 'users/send_confirmation.html'
email_template_name = 'users/confirmation_email.txt'
def get_context_data(self, **kwargs):
context = super(SendConfirmationView, self).get_context_data(**kwargs)
context['confirm_user'] = User.objects.get(id=kwargs['user_id'])
return context
def get(self, request, *args, **kwargs):
try:
user = User.objects.get(id=kwargs['user_id'])
except User.DoesNotExist:
raise Http404
if user.is_active:
raise Http404
uid = int_to_base36(user.pk)
token = default_token_generator.make_token(user)
link = request.build_absolute_uri(reverse('check_confirmation', kwargs={'user_id' : user.pk, 'token' : token}))
context = {
'email': user.email,
'validation_link': link,
'user': user
}
subject = "Validate your registration at %s" % site_name
email = loader.render_to_string(self.email_template_name, context)
user.email_user(subject,email)
return super(SendConfirmationView,self).get(self, request, *args, **kwargs)
class CheckConfirmationView(TemplateView):
template_name = 'users/check_confirmation.html'
def get_context_data(self, **kwargs):
context = super(CheckConfirmationView, self).get_context_data(**kwargs)
context['confirm_user'] = User.objects.get(id=kwargs['user_id'])
return context
def get(self, request, *args, **kwargs):
try:
user = User.objects.get(id=kwargs['user_id'])
except User.DoesNotExist:
raise Http404
if user.is_active:
raise Http404
if not default_token_generator.check_token(user,kwargs['token']):
raise Http404
user.is_active = True
user.save()
print("Acitvating %s" % user.username)
return super(CheckConfirmationView,self).get(self, request, *args, **kwargs)