rename things

This commit is contained in:
2018-03-22 21:35:33 +01:00
parent 858bfaec98
commit b652882bd0
24 changed files with 87 additions and 574 deletions

0
src/portal/__init__.py Normal file
View File

60
src/portal/admin.py Normal file
View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
from aldryn_forms.utils import get_user_model
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from parler.admin import TranslatableAdmin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from portal.models import Profile, Information, DownloadSection, DownloadTag, DownloadFile
User = get_user_model()
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
class UserAdmin(BaseUserAdmin):
inlines = [ProfileInline]
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups')}),
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
@admin.register(Information)
class InformationAdmin(TranslatableAdmin):
list_display = ('title', 'published', 'publish_date')
list_filter = ('groups',)
filter_horizontal = ('groups',)
readonly_fields = ('informed_users',)
fieldsets = (
(None, {'fields': ('title', 'published', 'image', 'cropping')}),
(_('Permissions'), {'fields': ('groups',)}),
(_('Veröffentlichung'), {'fields': ('publish_date',)}),
(_('Information'), {'fields': ('informed_users',)}),
)
@admin.register(DownloadSection)
class DownloadSectionAdmin(TranslatableAdmin):
list_display = ('title', 'ordering')
list_editable = ['ordering']
@admin.register(DownloadTag)
class DownloadTagAdmin(TranslatableAdmin):
pass
@admin.register(DownloadFile)
class DownloadFileAdmin(TranslatableAdmin):
list_display = ('label', 'ordering')
list_editable = ['ordering']
list_filter = ('section', 'groups')
filter_horizontal = ('groups', 'tags')

22
src/portal/forms.py Normal file
View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from portal.models import Profile
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
for _, value in self.fields.items():
value.widget.attrs['placeholder'] = value.label
class ProfileEditForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['first_name', 'last_name', 'street', 'zip', 'place', 'email']
def __init__(self, *args, **kwargs):
super(ProfileEditForm, self).__init__(*args, **kwargs)
self.fields['zip'].widget = forms.TextInput()

View File

142
src/portal/models.py Normal file
View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
from cms.models.fields import PlaceholderField
import os
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.files.storage import default_storage
from django.conf import settings
from django.db import models
from django.core.urlresolvers import reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from image_cropping import ImageRatioField
from parler.models import TranslatableModel, TranslatedFields
from portal.storage import PrivateS3MediaStorage
from project.utils import CroppableFilerImageField
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), related_name='profile', on_delete=models.CASCADE)
first_name = models.CharField(verbose_name=_('Vorname'), max_length=255)
last_name = models.CharField(verbose_name=_('Nachname'), max_length=255)
street = models.CharField(verbose_name=_('Strasse'), max_length=255, null=True, blank=True)
zip = models.IntegerField(verbose_name=_('PLZ'), null=True, blank=True)
place = models.CharField(verbose_name=_('Ort'), max_length=255, null=True, blank=True)
email = models.EmailField(verbose_name=_('E-Mail'), null=True, blank=True)
class Meta:
verbose_name = 'User Profil'
verbose_name_plural = 'User Profile'
def __str__(self):
return self.full_name
@property
def full_name(self):
return '{} {}'.format(self.first_name, self.last_name)
class Information(TranslatableModel):
groups = models.ManyToManyField(Group, verbose_name='Mitglieder Gruppe', related_name='tasks')
image = CroppableFilerImageField(verbose_name='Bild', blank=True, null=True)
cropping = ImageRatioField('image', '1200x800', free_crop=True)
placeholder = PlaceholderField('content')
published = models.BooleanField(verbose_name='Veröffentlicht', default=False)
publish_date = models.DateTimeField(verbose_name='Veröffentlichungsdatum', default=timezone.now)
informed_users = models.ManyToManyField(get_user_model(), verbose_name='Als gelesen markiert von:',
null=True, blank=True)
translations = TranslatedFields(
title=models.CharField(max_length=100, verbose_name=_('Title')),
)
class Meta:
verbose_name = 'Mitglieder Aufgabe'
verbose_name_plural = 'Mitglieder Aufgaben'
ordering = ['-publish_date']
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse_lazy('portal:information', args=[self.pk])
class DownloadSection(TranslatableModel):
translations = TranslatedFields(
title=models.CharField(max_length=100, verbose_name='Title')
)
ordering = models.IntegerField(default=50, verbose_name='Sortierung')
class Meta:
verbose_name = 'Download Section'
verbose_name_plural = 'Download Sections'
ordering = ['ordering']
def __str__(self):
return self.title
class DownloadTag(TranslatableModel):
translations = TranslatedFields(
name=models.CharField(max_length=100, verbose_name='Name')
)
class Meta:
verbose_name = 'Download Tag'
verbose_name_plural = 'Download Tags'
def __str__(self):
return self.name
if getattr(settings, 'DEFAULT_STORAGE_DSN', None):
protected_file_storage = PrivateS3MediaStorage()
else:
protected_file_storage = default_storage
class DownloadFile(TranslatableModel):
section = models.ForeignKey(DownloadSection, verbose_name='Download Section', related_name='files')
groups = models.ManyToManyField(Group, verbose_name='Mitgliedergruppen', related_name='files')
tags = models.ManyToManyField(DownloadTag, verbose_name='Suchbegriffe', related_name='files',
blank=True, null=True)
translations = TranslatedFields(
name=models.CharField(max_length=512, verbose_name='Name', blank=True, null=True),
description=models.TextField(verbose_name='Beschreibung', blank=True, null=True),
file=models.FileField(upload_to='protected_files', max_length=512, storage=protected_file_storage)
)
ordering = models.IntegerField(default=50, verbose_name='Sortierung')
class Meta:
verbose_name = 'Download File'
verbose_name_plural = 'Download Files'
ordering = ['ordering']
def __str__(self):
return self.label
@property
def url(self):
return self.file.url
@property
def extension(self):
return self.file.name.split('.')[-1]
@property
def label(self):
if self.name:
return self.name
else:
return os.path.basename(self.file.name)
@property
def tag_list(self):
return ', '.join(list(self.tags.values_list('translations__name', flat=True)))

57
src/portal/storage.py Normal file
View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
import re
from aldryn_django.storage import S3MediaStorage
from django.conf import settings
from boto.s3.connection import (
SubdomainCallingFormat,
OrdinaryCallingFormat,
)
class PrivateS3MediaStorage(S3MediaStorage):
def __init__(self):
bucket_name = settings.AWS_MEDIA_STORAGE_BUCKET_NAME
if '.' in bucket_name:
calling_format = OrdinaryCallingFormat()
else:
calling_format = SubdomainCallingFormat()
# We cannot use a function call or a partial here. Instead, we have to
# create a subclass because django tries to recreate a new object by
# calling the __init__ of the returned object (with no arguments).
super(S3MediaStorage, self).__init__(
access_key=settings.AWS_MEDIA_ACCESS_KEY_ID,
secret_key=settings.AWS_MEDIA_SECRET_ACCESS_KEY,
bucket_name=bucket_name,
location=settings.AWS_MEDIA_BUCKET_PREFIX,
host=settings.AWS_MEDIA_STORAGE_HOST,
custom_domain=settings.AWS_MEDIA_DOMAIN,
calling_format=calling_format,
# Setting an ACL requires us to grant the user the PutObjectAcl
# permission as well, even if it matches the default bucket ACL.
# XXX: Ideally we would thus set it to `None`, but due to how
# easy_thumbnails works internally, that causes thumbnail
# generation to fail...
default_acl='private',
querystring_auth=True,
)
# MEDIA_HEADERS is a list of tuples containing a regular expression
# to match against a path, and a dictionary of HTTP headers to be
# returned with the resource identified by the path when it is
# requested.
# The headers are applied in the order they where declared, and
# processing stops at the first match.
# E.g.:
#
# MEDIA_HEADERS = [
# (r'media/cache/.*', {
# 'Cache-Control': 'max-age={}'.format(3600 * 24 * 365),
# })
# ]
#
media_headers = getattr(settings, 'MEDIA_HEADERS', [])
self.media_headers = [
(re.compile(r), headers) for r, headers in media_headers
]

View File

@@ -0,0 +1,13 @@
{% load i18n %}
<form action="{{ request.path }}" method="post">
{% csrf_token %}
<div class="control__item__fields">
{% for field in form %}
{% include 'project/includes/field.html' with field=field label=True %}
{% endfor %}
</div>
<button class="button button--small">
<span class="button__icon">{% include 'project/assets/tick.svg' %}</span>
<span class="button__text">{% trans 'Speichern' %}</span>
</button>
</form>

View File

@@ -0,0 +1,8 @@
{% load i18n %}
<div class="control__item__success">
<p>{% trans 'Ihre Informationen wurden erfolgreich angepasst.' %}</p>
<a href="#" class="button button--small control__item__close">
<span class="button__icon">{% include 'project/assets/close.svg' %}</span>
<span class="button__text">{% trans 'Schliessen' %}</span>
</a>
</div>

View File

@@ -0,0 +1,44 @@
{% extends 'project/content.html' %}
{% load i18n thumbnail cms_tags %}
{% block title %}{{ object.title }}{% endblock %}
{% block content_intro %}
<div class="content__intro reveal_container reveal_self reveal reveal_animation{% if object.image %} image{% endif %}">
<div class="content__intro__content reveal reveal_animation">
<h1>{{ object.title }}</h1>
<a href="{% url 'portal:overview' %}" class="button">
<span class="button__icon">{% include 'project/assets/arrow-left-long.svg' %}</span>
<span class="button__text">{% trans 'Zurück zur Übersicht' %}</span>
</a>
</div>
{% if object.image %}
{% thumbnail object.image 1600x800 box=object.cropping crop detail as thumb %}
<div class="content__intro__image scroll reveal reveal_animation" data-ease-multiplier="-2"
style="background-image: url({{ thumb.url }})"></div>
{% endif %}
</div>
{% endblock %}
{% block navigation_title %}{{ object.title }}{% endblock %}
{% block content_main %}
{% render_placeholder object.placeholder language LANGUAGE_CODE %}
<form action="{{ request.path }}" method="post" class="information__form reveal_self reveal reveal_animation">
{% csrf_token %}
<div class="form__submit">
<a href="{% url 'portal:overview' %}" class="button button--ghost">
<span class="button__icon">{% include 'project/assets/arrow-left-long.svg' %}</span>
<span class="button__text">{% trans 'Zurück zur Übersicht' %}</span>
</a>
{% if not request.user in object.informed_users.all %}
<button class="button">
<span class="button__icon">{% include 'project/assets/tick.svg' %}</span>
<span class="button__text">{% trans 'Als gelesen markieren' %}</span>
</button>
{% endif %}
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,117 @@
{% extends 'main.html' %}
{% load i18n static %}
{% block title %}{% trans 'Mitgliederbereich' %}{% endblock %}
{% block content %}
<div class="content__frame">
<div class="content__intro reveal_container reveal_self reveal reveal_animation image">
<div class="content__intro__content reveal reveal_animation">
<h1>{% trans 'Grüezi, ' %}{{ request.user.profile.full_name }}</h1>
<p>{% trans 'Willkommen in Ihrem persönlichen Portal der Tagesschule Elementa' %}</p>
<a href="{% url 'portal:logout' %}" class="button">
<span class="button__icon">{% include 'project/assets/arrow-left-long.svg' %}</span>
<span class="button__text">{% trans 'Logout' %}</span>
</a>
</div>
<div class="content__intro__image scroll reveal reveal_animation" data-ease-multiplier="-2"
style="background-image: url({% static 'img/portal_background.jpg' %})"></div>
</div>
<div class="content__container">
<div class="content__main">
<div class="control_panel reveal_self reveal reveal_animation">
<div class="control_panel__content">
<div class="control_panel__content__item">
<h2 class="reveal_self reveal reveal_animation">{% trans 'Aktuell' %}</h2>
<div id="todo" class="load__frame">
<div class="load__main">
<ul class="control__list reveal_container">
{% for object in object_list %}
<li class="data_id_{{ forloop.counter0 }} reveal reveal_animation">
<a href="{{ object.get_absolute_url }}"
class="p control__item control__item--arrow control__item--status {% if request.user in object.informed_users.all %}control__item--status--active{% endif %}">
<span class="control__item__title">
<span class="control__item__status">
{% if object.is_expired %}
{% include 'project/assets/close.svg' %}
{% else %}
{% include 'project/assets/tick.svg' %}
{% endif %}
</span>
{{ object.title }}
<span class="control__item__arrow">
{% include 'project/assets/arrow-right.svg' %}
</span>
</span>
</a>
</li>
{% endfor %}
</ul>
{% if page_obj.has_next or paginator.count > 0 and open_object_list %}
<div class="load__replace data_id_3 reveal_self reveal reveal_animation">
<a href="{% spaceless %}{% url 'portal:overview' %}?page=
{% if open_object_list %}{{ page_obj.start_index }}{% else %}{{ page_obj.next_page_number }}{% endif %}{% endspaceless %}"
class="button button--load list__button button--small">
<span class="button__icon">{% include 'project/assets/dots.svg' %}</span>
<span class="button__text">{% trans 'Ältere laden' %}</span>
</a>
</div>
{% endif %}
</div>
</div>
</div>
<div class="control_panel__content__item">
<h2 class="reveal_self reveal reveal_animation">{% trans 'Einstellungen' %}</h2>
<ul class="control__list reveal_container">
{% for settings_title, settings_url in settings %}
<li class="reveal reveal_animation data_delay_{{ forloop.counter0 }}">
<div class="control__item control__item--button">
<span class="control__item__title">
{{ settings_title }}
<a href="#" data-href="{{ settings_url }}"
class="button button--small control__item__open">
<span class="button__icon">{% include 'project/assets/dots.svg' %}</span>
<span class="button__text">{% trans 'Ändern' %}</span>
</a>
<a href="#"
class="button button--small button--ghost control__item__close">
<span class="button__icon">{% include 'project/assets/close.svg' %}</span>
<span class="button__text">{% trans 'Abbrechen' %}</span>
</a>
</span>
<div class="control__item__content">
<div class="control__item__content__main">
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<div id="downloads" class="downloads__frame reveal_self reveal reveal_animation">
<h2 class="reveal_self reveal reveal_animation">{% trans 'Downloads' %}</h2>
<form class="downloads__filter reveal_self reveal reveal_animation"
action="{{ request.path }}#downloads" method="get">
<div class="form__field form__field--icon">
<input name="q" {% if request.GET.q %}value="{{ request.GET.q }}"{% endif %}
id="downloads_search"
type="text" placeholder="{% trans 'Suchbegriff eingeben' %}">
<button>{% trans 'Suchen' %}{% include 'project/assets/search.svg' %}</button>
</div>
</form>
{% for section in download_sections %}
<div class="downloads__section reveal_container">
<div class="downloads__section__title{% if flat %} downloads__section__title--flat{% endif %} reveal reveal_animation">
<h3>{{ section.title }}</h3>
</div>
{% include 'project/plugins/content/download_section.html' with instance=section %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,36 @@
{% extends 'project/dialog.html' %}
{% load i18n %}
{% block title %}{% trans 'Mitgliederbereich' %}{% endblock %}
{% block extra_meta %}
<meta name="robots" content="noindex, nofollow"/>
{% endblock %}
{% block dialog %}
<h2 class="reveal_self reveal reveal_animation">{% trans 'Login' %}</h2>
<p class="reveal_self reveal reveal_animation section__text">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr.
</p>
<form class="reveal_self reveal reveal_animation" method="post" action=".">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="form__errors reveal_self reveal reveal_animation">{{ error }}</p>
{% endfor %}
{% endif %}
{% csrf_token %}
{% for field in form %}
{% include 'project/includes/field.html' with field=field %}
{% endfor %}
<div class="form__submit reveal_self reveal reveal_animation">
<button type="submit" class="button">
<span class="button__icon">{% include 'project/assets/arrow-right-long.svg' %}</span>
<span class="button__text">{% trans 'Login' %}</span>
</button>
</div>
</form>
{% endblock %}

37
src/portal/urls.py Normal file
View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView
from portal.forms import LoginForm
from portal.views import ProfileEditView, OverviewView, InformationDetailView
urlpatterns = [
url(_(r'^login/$'), LoginView.as_view(form_class=LoginForm), name='login'),
url(_(r'^logout/$'), LogoutView.as_view(next_page=reverse_lazy('portal:overview')), name='logout'),
url(_(r'^account/change/password/$'), login_required(PasswordChangeView.as_view(
template_name='portal/edit_form.html',
success_url=reverse_lazy('portal:change_password_success')
), login_url=reverse_lazy('portal:login')), name='change_password'),
url(_(r'^account/change/password/success/$'), login_required(TemplateView.as_view(
template_name='portal/edit_success.html'
), login_url=reverse_lazy('portal:login')), name='change_password_success'),
url(_(r'^account/edit/profile/$'), login_required(ProfileEditView.as_view(
), login_url=reverse_lazy('portal:login')), name='edit_profile'),
url(_(r'^account/edit/profile/success/$'), login_required(TemplateView.as_view(
template_name='portal/edit_success.html'
), login_url=reverse_lazy('portal:login')), name='edit_profile_success'),
url(_(r'^info/(?P<pk>\d+)/$'),
login_required(InformationDetailView.as_view(), login_url=reverse_lazy('portal:login')), name='information'),
url(_(r'^$'), login_required(OverviewView.as_view(), login_url=reverse_lazy('portal:login')), name='overview'),
]

84
src/portal/views.py Normal file
View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
from django.shortcuts import redirect
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView, ListView, DetailView
from portal.forms import ProfileEditForm
from portal.models import Information, DownloadFile
class InformationQuerysetMixin(object):
def get_queryset(self):
queryset = Information.objects.filter(groups__in=self.request.user.groups.all())
if not self.request.user.is_superuser:
queryset = queryset.filter(published=True)
return queryset
class OverviewView(InformationQuerysetMixin, ListView):
template_name = 'portal/overview.html'
open_tasks = None
paginate_by = 5
def get_download_sections(self):
file_list = DownloadFile.objects.filter(groups__in=self.request.user.groups.all())
sections = {}
for file in file_list:
if not sections.get(file.section_id, None):
sections[file.section_id] = {
'section': file.section,
'items': []
}
sections[file.section_id]['items'].append(file)
sections_list = [{'title': x['section'].title, 'items': x['items'], 'ordering': x['section'].ordering} for x in
sections.values()]
return sorted(sections_list, key=lambda x: x['ordering'])
def get_queryset(self):
queryset = super(OverviewView, self).get_queryset()
return queryset.filter(informed_users__in=[self.request.user])
def get_open_tasks(self):
queryset = InformationQuerysetMixin.get_queryset(self).exclude(informed_users__in=[self.request.user])
return queryset
def get_settings(self):
settings = (
(_('Benutzerdaten'), reverse_lazy('portal:edit_profile')),
(_('Passwort'), reverse_lazy('portal:change_password')),
)
return settings
def get_context_data(self, **kwargs):
context = super(OverviewView, self).get_context_data(**kwargs)
context.update({
'download_sections': self.get_download_sections(),
'settings': self.get_settings()
})
self.open_tasks = self.get_open_tasks()
if not self.request.GET.get(self.page_kwarg, None) and self.open_tasks.count() > 0:
context.update({
'object_list': self.open_tasks,
'open_object_list': True
})
return context
class InformationDetailView(InformationQuerysetMixin, DetailView):
template_name = 'portal/information.html'
def post(self, request, *args, **kwargs):
self.get_object().informed_users.add(self.request.user)
return redirect('portal:overview')
class ProfileEditView(UpdateView):
form_class = ProfileEditForm
template_name = 'portal/edit_form.html'
success_url = reverse_lazy('portal:edit_profile_success')
def get_object(self, queryset=None):
return self.request.user.profile