diff --git a/jdav_web/jdav_web/settings.py b/jdav_web/jdav_web/settings.py index c38a5db..5117205 100644 --- a/jdav_web/jdav_web/settings.py +++ b/jdav_web/jdav_web/settings.py @@ -62,7 +62,7 @@ ROOT_URLCONF = 'jdav_web.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -129,6 +129,9 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/' +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static") +] # Locale files (translations) diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..902c89c --- /dev/null +++ b/jdav_web/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-01-14 17:13+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/admin/base_site.html:5 templates/admin/base_site.html:9 +msgid "JDAV Administration" +msgstr "JDAV Verwaltung" diff --git a/jdav_web/mailer/admin.py b/jdav_web/mailer/admin.py index ffb476a..c12c4b1 100644 --- a/jdav_web/mailer/admin.py +++ b/jdav_web/mailer/admin.py @@ -1,16 +1,27 @@ -from django.contrib import admin +from django.contrib import admin, messages from django.contrib.admin import helpers from django.utils.translation import ugettext_lazy as _ from django.shortcuts import render +from django.db import models +from django import forms -from .models import Message +from .models import Message, Attachment + + +class AttachmentInline(admin.StackedInline): + model = Attachment + extra = 0 class MessageAdmin(admin.ModelAdmin): """Message creation view""" list_display = ('subject', 'from_addr', 'get_groups', 'sent') change_form_template = "mailer/change_form.html" + formfield_overrides = { + models.ManyToManyField: {'widget': forms.CheckboxSelectMultiple} + } + inlines = [AttachmentInline] actions = ['send_message'] def send_message(self, request, queryset): @@ -30,13 +41,19 @@ class MessageAdmin(admin.ModelAdmin): def response_change(self, request, obj): if "_send" in request.POST: - obj.submit() + if not obj.submit(): + messages.error(request, _("Failed to send message")) + else: + messages.info(request, _("Successfully sent message")) return super(MessageAdmin, self).response_change(request, obj) def response_add(self, request, obj): if "_send" in request.POST: - obj.submit() - return super(MessageAdmin, self).response_change(request, obj) + if not obj.submit(): + messages.error(request, _("Failed to send message")) + else: + messages.info(request, _("Successfully sent message")) + return super(MessageAdmin, self).response_add(request, obj) admin.site.register(Message, MessageAdmin) diff --git a/jdav_web/mailer/locale/de/LC_MESSAGES/django.po b/jdav_web/mailer/locale/de/LC_MESSAGES/django.po index fd5005e..e989f3f 100644 --- a/jdav_web/mailer/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/mailer/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-11-19 16:11+0100\n" +"POT-Creation-Date: 2017-01-14 17:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,104 +18,204 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin.py:20 +#: mailer/admin.py:32 msgid "Message sent" msgstr "Nachricht gesendet" -#: admin.py:28 +#: mailer/admin.py:40 msgid "Send message" msgstr "Nachricht verschicken" -#: apps.py:7 +#: mailer/admin.py:45 mailer/admin.py:53 +msgid "Failed to send message" +msgstr "Fehler beim senden der Email" + +#: mailer/admin.py:47 mailer/admin.py:55 +msgid "Successfully sent message" +msgstr "Email wurde erfolgreich verschickt" + +#: mailer/apps.py:7 msgid "mailer" msgstr "Verteiler" -#: models.py:9 +#: mailer/models.py:34 msgid "from email" msgstr "Von Email" -#: models.py:10 +#: mailer/models.py:35 msgid "subject" msgstr "Betreff" -#: models.py:11 +#: mailer/models.py:36 msgid "content" msgstr "Inhalt" -#: models.py:12 +#: mailer/models.py:38 msgid "to group" msgstr "An Gruppe" -#: models.py:13 +#: mailer/models.py:39 msgid "sent" msgstr "Gesendet" -#: models.py:29 +#: mailer/models.py:46 +msgid "recipients" +msgstr "Empfänger" + +#: mailer/models.py:74 msgid "message" msgstr "Nachricht" -#: models.py:30 +#: mailer/models.py:75 msgid "messages" msgstr "Nachrichten" -#: models.py:32 +#: mailer/models.py:77 msgid "Can submit mails" msgstr "Kann Mails verschicken" -#: templates/admin/change_form.html:11 +#: mailer/models.py:86 +msgid "file" +msgstr "Datei" + +#: mailer/models.py:92 +msgid "Empty" +msgstr "Leer" + +#: mailer/models.py:95 +msgid "attachment" +msgstr "Anhang" + +#: mailer/models.py:96 +msgid "attachments" +msgstr "Anhänge" + +#: mailer/templates/mailer/change_form.html:11 msgid "Save and send mail" msgstr "Speichern und Email senden" -#: templates/mailer/confirm_send.html:7 +#: mailer/templates/mailer/confirm_send.html:7 msgid "Do you really want to send these mails?" msgstr "Möchtest du diese Emails wirklich verschicken?" -#: templates/mailer/confirm_send.html:13 +#: mailer/templates/mailer/confirm_send.html:13 msgid "already sent" msgstr "schon verschickt" -#: templates/mailer/confirm_send.html:19 +#: mailer/templates/mailer/confirm_send.html:19 msgid "" "Some messages have already been sent! Do you really want to resend them?" msgstr "" "Einige Emails wurden schon versendet! Möchtest du diese wirklich nochmal " "senden?" -#: templates/mailer/confirm_send.html:30 +#: mailer/templates/mailer/confirm_send.html:30 msgid "Send" msgstr "Senden" -#: templates/mailer/confirm_send.html:35 +#: mailer/templates/mailer/confirm_send.html:35 msgid "Cancel" msgstr "Abbruch" -#: templates/mailer/index.html:2 +#: mailer/templates/mailer/confirmation_sent.html:4 +#: mailer/templates/mailer/unsubscribe.html:5 +#: mailer/templates/mailer/unsubscribe.html:25 +msgid "Unsubscribe" +msgstr "Vom Newsletter abmelden" + +#: mailer/templates/mailer/confirmation_sent.html:7 +msgid "Sent confirmation mail to" +msgstr "Bestätigungsmail gesendet an" + +#: mailer/templates/mailer/confirmation_sent.html:7 +msgid "Follow the link in your mail to confirm your unsubscription." +msgstr "" + +#: mailer/templates/mailer/index.html:2 msgid "This is the mailer app!" msgstr "Das ist die Mailer App!" -#: templates/mailer/send.html:2 +#: mailer/templates/mailer/send.html:2 msgid "Here you can send new emails!" msgstr "Hier kannst du neue Emails verschicken!" -#: templates/mailer/send.html:11 +#: mailer/templates/mailer/send.html:11 msgid "Subject:" msgstr "Betreff" -#: templates/mailer/send.html:14 +#: mailer/templates/mailer/send.html:14 msgid "Content:" msgstr "Inhalt:" -#: templates/mailer/send.html:17 +#: mailer/templates/mailer/send.html:17 msgid "Receiving group:" msgstr "Erhaltende Gruppe" -#: templates/mailer/send.html:24 +#: mailer/templates/mailer/send.html:24 msgid "Send mail" msgstr "Email senden" -#: views.py:33 +#: mailer/templates/mailer/subscribe.html:5 +msgid "Here you can register yourself to the newsletter" +msgstr "Hier kannst du dich für den Newsletter anmelden." + +#: mailer/templates/mailer/subscribe.html:16 +msgid "Prename" +msgstr "Vorname" + +#: mailer/templates/mailer/subscribe.html:21 +msgid "Lastname" +msgstr "Nachname" + +#: mailer/templates/mailer/subscribe.html:26 mailer/views.py:61 +msgid "Birthdate" +msgstr "Geburtsdatum" + +#: mailer/templates/mailer/subscribe.html:37 +#: mailer/templates/mailer/unsubscribe.html:20 +msgid "Email address" +msgstr "Email-Adresse" + +#: mailer/templates/mailer/subscribe.html:42 +msgid "Register" +msgstr "Registrieren" + +#: mailer/templates/mailer/subscribed.html:3 +msgid "Subscribed successfully" +msgstr "Erfolgreich angemeldet" + +#: mailer/templates/mailer/unsubscribe.html:9 +msgid "Here you can unsubscribe from the newsletter" +msgstr "Hier kannst du dich vom Newsletter abmelden" + +#: mailer/templates/mailer/unsubscribed.html:8 +msgid "Successfully unsubscribed from the newsletter for " +msgstr "Newsletter erfolgreich abbestellt für " + +#: mailer/views.py:35 +msgid "Can't verify this link. Try again!" +msgstr "Ungültiger Link. Bitte nochmal versuchen!" + +#: mailer/views.py:47 +msgid "Please fill in every field" +msgstr "Bitte jedes Feld ausfüllen!" + +#: mailer/views.py:84 msgid "Please fill in every field!" msgstr "Bitte jedes Feld ausfüllen!" +#: mailer/views.py:91 +msgid "Member already exists" +msgstr "Mitglied schon vorhanden" + +#~ msgid "Confirmation of unsubscription" +#~ msgstr "Abmeldebestätigung" + +#~ msgid "" +#~ "Click the link to unsubscribe to the newsletter of the JDAV\n" +#~ "{}" +#~ msgstr "" +#~ "Klicke auf den Link um dich von Newsletter des JDAV Ludwigsburg abzumelden" + #~ msgid "History" #~ msgstr "Geschichte" diff --git a/jdav_web/mailer/mailutils.py b/jdav_web/mailer/mailutils.py index bd47c74..8878b81 100644 --- a/jdav_web/mailer/mailutils.py +++ b/jdav_web/mailer/mailutils.py @@ -1,16 +1,26 @@ -from django.core.mail import send_mass_mail, send_mail - - -def send(subject, content, sender, recipicient): - send_mail(subject, content, sender, [recipicient]) - - -def send_mass(subject, content, sender, recipicients): - data = [ - (subject, content, sender, [recipicient]) - for recipicient in recipicients] - print("sending data", data) - send_mass_mail(data) +from django.core.mail import EmailMessage + + +def send(subject, content, sender, recipients, reply_to=None, + attachments=None): + if type(recipients) != list: + recipients = [recipients] + if reply_to is not None: + kwargs = {"reply_to": [reply_to]} + else: + kwargs = {} + for recipient in recipients: + email = EmailMessage(subject, content, sender, [recipient], **kwargs) + if attachments is not None: + for attach in attachments: + email.attach_file(attach) + try: + email.send() + except Exception as e: + print("Error when sending mail:", e) + return False + else: + return True def get_content(content): diff --git a/jdav_web/mailer/models.py b/jdav_web/mailer/models.py index 10770fc..d76ae5f 100644 --- a/jdav_web/mailer/models.py +++ b/jdav_web/mailer/models.py @@ -1,6 +1,31 @@ from django.db import models +from django.forms import forms from django.utils.translation import ugettext_lazy as _ -from .mailutils import send_mass, get_content +from .mailutils import send, get_content + +import os + + +class RestrictedFileField(models.FileField): + + def __init__(self, *args, **kwargs): + if "max_upload_size" in kwargs: + self.max_upload_size = kwargs.pop("max_upload_size") + + super(RestrictedFileField, self).__init__(*args, **kwargs) + + def clean(self, *args, **kwargs): + data = super(RestrictedFileField, self).clean(*args, **kwargs) + f = data.file + try: + if f._size > self.max_upload_size: + raise forms.ValidationError('Please keep filesize under {}. ' + 'Current filesize: ' + '{}'.format(self.max_upload_size, + f._size)) + except AttributeError as e: + print(e) + return data # Create your models here. @@ -29,10 +54,21 @@ class Message(models.Model): if not member.gets_newsletter: continue members.add(member) - send_mass(self.subject, get_content(self.content), - self.from_addr, [member.email for member in members]) - self.sent = True - self.save() + attach = [a.f.path for a in Attachment.objects.filter(msg__id=self.pk) + if a.f.name] + success = send(self.subject, get_content(self.content), + self.from_addr, [member.email for member in members], + attachments=attach) + for a in Attachment.objects.filter(msg__id=self.pk): + if a.f.name: + os.remove(a.f.path) + a.delete() + if success: + self.sent = True + self.save() + return True + else: + return False class Meta: verbose_name = _('message') @@ -40,3 +76,21 @@ class Message(models.Model): permissions = ( ("submit_mails", _("Can submit mails")), ) + + +class Attachment(models.Model): + """Represents an attachment to an email""" + msg = models.ForeignKey(Message, on_delete=models.CASCADE) + print("attachment class") + # file (not naming it file because of builtin) + f = RestrictedFileField(_('file'), + upload_to='attachments', + blank=True, + max_upload_size=10485760) + + def __str__(self): + return os.path.basename(self.f.name) if self.f.name else _("Empty") + + class Meta: + verbose_name = _('attachment') + verbose_name_plural = _('attachments') diff --git a/jdav_web/mailer/templates/mailer/confirmation_sent.html b/jdav_web/mailer/templates/mailer/confirmation_sent.html index 8f6b32d..8d40c4f 100644 --- a/jdav_web/mailer/templates/mailer/confirmation_sent.html +++ b/jdav_web/mailer/templates/mailer/confirmation_sent.html @@ -1,3 +1,7 @@ {% load i18n %} + + {% trans "Unsubscribe" %} + +

{% trans "Sent confirmation mail to" %} {{ email }}. {% trans "Follow the link in your mail to confirm your unsubscription." %}

diff --git a/jdav_web/mailer/templates/mailer/unsubscribe.html b/jdav_web/mailer/templates/mailer/unsubscribe.html index 5055820..53ba56e 100644 --- a/jdav_web/mailer/templates/mailer/unsubscribe.html +++ b/jdav_web/mailer/templates/mailer/unsubscribe.html @@ -1,5 +1,11 @@ {% load i18n %} + + + {% trans "Unsubscribe" %} + + +

{% trans "Here you can unsubscribe from the newsletter" %}

{% if error_message %} diff --git a/jdav_web/mailer/templates/mailer/unsubscribed.html b/jdav_web/mailer/templates/mailer/unsubscribed.html index 9c05d11..d7e4bd8 100644 --- a/jdav_web/mailer/templates/mailer/unsubscribed.html +++ b/jdav_web/mailer/templates/mailer/unsubscribed.html @@ -1,5 +1,9 @@ {% load i18n %} + + {% trans Unsubscribe %} + +

{% trans "Successfully unsubscribed from the newsletter for " %}{{ email }}

diff --git a/jdav_web/mailer/views.py b/jdav_web/mailer/views.py index 00f2e63..b8500d5 100644 --- a/jdav_web/mailer/views.py +++ b/jdav_web/mailer/views.py @@ -46,9 +46,10 @@ def unsubscribe(request): except (KeyError, Member.DoesNotExist): return render_unsubscribe(request, _("Please fill in every field")) else: - send_mail(_("Confirmation of unsubscription"), - _("Click the link to unsubscribe to the newsletter of" - " the JDAV\n{}".format(get_unsubscribe_link(member))), + send_mail("Abmeldebestätigung", + "Klicke auf den Link, um dich vom Newsletter des JDAV " + "Ludwigsburg " + "abzumelden\n{}".format(get_unsubscribe_link(member)), mail_root, email) return render_confirmation_sent(request, email) diff --git a/jdav_web/material/admin.py b/jdav_web/material/admin.py index f285118..0771630 100644 --- a/jdav_web/material/admin.py +++ b/jdav_web/material/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from django.utils. translation import ugettext_lazy as translate +from django.utils.translation import ugettext_lazy as _ from django.contrib.admin import SimpleListFilter from .models import MaterialPart, Ownership @@ -16,13 +16,13 @@ class OwnershipInline(admin.StackedInline): class NotTooOldFilter(SimpleListFilter): - title = translate('Age') + title = _('Age') parameter_name = 'age' def lookups(self, request, model_admin): return ( - ('too_old', translate('Not Too Old')), - ('not_too_old', translate('Too old')), + ('too_old', _('Not too old')), + ('not_too_old', _('Too old')), ) def queryset(self, request, queryset): @@ -31,7 +31,7 @@ class NotTooOldFilter(SimpleListFilter): if self.value() == 'not_too_old': return queryset.filter(pk__in=[x.pk for x in queryset.all() if not x.not_too_old()]) - + class MaterialAdmin(admin.ModelAdmin): """Edit view of a MaterialPart""" @@ -41,4 +41,4 @@ class MaterialAdmin(admin.ModelAdmin): list_filter = (NotTooOldFilter,) -admin.site.register(MaterialPart, MaterialAdmin) \ No newline at end of file +admin.site.register(MaterialPart, MaterialAdmin) diff --git a/jdav_web/material/locale/de/LC_MESSAGES/django.po b/jdav_web/material/locale/de/LC_MESSAGES/django.po index fd7587b..9dc1658 100644 --- a/jdav_web/material/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/material/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-10-21 23:38+0200\n" +"POT-Creation-Date: 2017-01-14 17:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,50 +18,70 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:7 +#: material/admin.py:19 +msgid "Age" +msgstr "Alter" + +#: material/admin.py:24 +msgid "Not too old" +msgstr "Nicht zu alt" + +#: material/admin.py:25 +msgid "Too old" +msgstr "Zu alt" + +#: material/apps.py:7 msgid "material" msgstr "Material" -#: models.py:17 +#: material/models.py:17 msgid "name" msgstr "Name" -#: models.py:18 +#: material/models.py:18 +msgid "quantity" +msgstr "Anzahl" + +#: material/models.py:19 msgid "purchase date" msgstr "Kaufdatum" -#: models.py:19 +#: material/models.py:20 msgid "lifetime (years)" msgstr "Lebenszeit (Jahre)" -#: models.py:21 +#: material/models.py:21 msgid "photo" msgstr "Bild" -#: models.py:35 +#: material/models.py:32 +msgid "Quantity" +msgstr "Anzahl" + +#: material/models.py:42 msgid "Not too old?" msgstr "Nicht zu alt?" -#: models.py:38 +#: material/models.py:45 msgid "material part" msgstr "Materialteil" -#: models.py:39 +#: material/models.py:46 msgid "material parts" msgstr "Materialteile" -#: models.py:45 +#: material/models.py:52 msgid "owner" msgstr "Besitzer" -#: models.py:46 +#: material/models.py:53 msgid "count" msgstr "Anzahl" -#: models.py:53 +#: material/models.py:60 msgid "ownership" msgstr "Besitztum" -#: models.py:54 +#: material/models.py:61 msgid "ownerships" msgstr "Besitztümer" diff --git a/jdav_web/material/migrations/0001_initial.py b/jdav_web/material/migrations/0001_initial.py index 7f71953..77e4e51 100644 --- a/jdav_web/material/migrations/0001_initial.py +++ b/jdav_web/material/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.2 on 2016-10-18 19:07 +# Generated by Django 1.10.2 on 2016-11-19 14:56 from __future__ import unicode_literals from django.db import migrations, models @@ -11,7 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('members', '0004_auto_20161018_1744'), + ('members', '0001_initial'), ] operations = [ @@ -19,17 +19,27 @@ class Migration(migrations.Migration): name='MaterialPart', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), + ('name', models.CharField(max_length=30, verbose_name='name')), ('buy_date', models.DateField(verbose_name='purchase date')), + ('lifetime', models.DecimalField(decimal_places=0, max_digits=3, verbose_name='lifetime (years)')), + ('photo', models.ImageField(blank=True, upload_to='images', verbose_name='photo')), ], + options={ + 'verbose_name_plural': 'material parts', + 'verbose_name': 'material part', + }, ), migrations.CreateModel( name='Ownership', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('count', models.IntegerField(default=1)), + ('count', models.IntegerField(default=1, verbose_name='count')), ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='material.MaterialPart')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Member')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Member', verbose_name='owner')), ], + options={ + 'verbose_name_plural': 'ownerships', + 'verbose_name': 'ownership', + }, ), ] diff --git a/jdav_web/material/models.py b/jdav_web/material/models.py index cc6226b..3e59978 100644 --- a/jdav_web/material/models.py +++ b/jdav_web/material/models.py @@ -25,13 +25,12 @@ class MaterialPart(models.Model): return self.name def quantity_real(self): - real = sum([o.count for o in Ownership.objects.filter(material__id = self.pk)]) + real = sum([o.count for o in Ownership.objects.filter(material__id=self.pk)]) return str(real) + '/' + str(self.quantity) quantity_real.admin_order_field = 'quantity' quantity_real.short_description = _('Quantity') - def not_too_old(self): """Returns wether the part should be replaced cause of age""" buy_time = timezone.make_aware(datetime.combine(self.buy_date, diff --git a/jdav_web/media/memberlists/dav_logo.png b/jdav_web/media/memberlists/dav_logo.png new file mode 100644 index 0000000..8039b16 Binary files /dev/null and b/jdav_web/media/memberlists/dav_logo.png differ diff --git a/jdav_web/media/memberlists/memberlist_template.tex b/jdav_web/media/memberlists/memberlist_template.tex index e0c629c..67c2456 100644 --- a/jdav_web/media/memberlists/memberlist_template.tex +++ b/jdav_web/media/memberlists/memberlist_template.tex @@ -1,21 +1,76 @@ \documentclass{article} +\usepackage[utf8]{inputenc} \usepackage{booktabs} +\usepackage{amssymb} \usepackage{cmbright} -\usepackage[margin=1in,landscape]{geometry} +\usepackage{graphicx} +\usepackage{textpos} +\usepackage[colorlinks]{hyperref} +\usepackage{float} +\usepackage[margin=1in]{geometry} +\newcommand{\picpos}[4]{ + \begin{textblock*}{#1}(#2, #3) + \includegraphics[width=\textwidth]{#4} + \end{textblock*} +} + +\renewcommand{\arraystretch}{1.5} + +\newcommand{\tickedbox}{ + \makebox[0pt][l]{$\square$}\raisebox{.15ex}{\hspace{0.1em}$\checkmark$} +} +\newcommand{\checkbox}{ + \makebox[0pt][l]{$\square$} +} \begin{document} -\noindent -{\LARGE\textsc{MEMBERLIST-TITLE}}\\ +% HEADER RIGHT +\picpos{4.5cm}{11.5cm}{0cm}{dav_logo.png} +\begin{textblock*}{5cm}(11.5cm, 2.3cm) + \begin{flushright} + \small + \noindent Deutscher Alpenverein e. V. \\ + Sektion Ludwigsburg\\ + Imbröderstraße 14\\ + 71634 Ludwigsburg\\ + Tel.: 07141 927893\\ + Fax: 07141 924042\\ + info@alpenverein-ludwigsburg.de\\ + \end{flushright} +\end{textblock*} + +% HEADLINE +{\noindent\LARGE\textsc{Teilnehmerliste \\Sektionsveranstaltung}}\\ \textit{Erstellt: MEMBERLIST-DATE}\\ -MEMBERLIST-COMMENTS -\begin{table}[h] - \begin{tabular}{llll} + +% DESCRIPTION TABLE +\begin{table}[H] + \begin{tabular}{ll} + \large Aktivität: & ACTIVITY \\ + \large Gruppe: & GROUP \\ + \large Ziel: & DESTINATION \\ + \large Stützpunkt: & PLACE \\ + \large Zeitraum: & TIME-PERIOD \\ + \large Betreuer: & JUGENDLEITER \\ + \end{tabular} +\end{table} + +\noindent TOUR-TYPE +\begin{table}[H] + \begin{tabular*}{1\linewidth}{@{\extracolsep{\fill}}llll} \toprule - Name & Vorname & Geburtsdatum & Kommentare \\ + \textbf{Name} & \textbf{Anschrift} & \textbf{Telefon} & \textbf{E-Mail} \\ \midrule \input{TABLE-NAME} \bottomrule - \end{tabular} + \end{tabular*} \end{table} + +\vspace{1cm} + +\noindent Bitte die ausgefüllte Teilnehmerliste vor Antritt der Aktivität per E-Mail an +\href{mailto:info@alpenverein-ludwigsburg.de}{info@alpenverein.de} und +\href{mailto:vorstand@alpenverein-ludwigsburg.de}{vorstand@alpenverein-ludwigsburg.de} senden. + \end{document} diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index e9f3b48..6bc5edc 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -11,7 +11,7 @@ from django import forms from django.contrib import admin from django.contrib.admin import DateFieldListFilter from django.utils.translation import ugettext_lazy as translate -from django.db.models import TextField +from django.db.models import TextField, ManyToManyField from django.forms import Textarea from django.shortcuts import render @@ -23,9 +23,11 @@ from .models import (Member, Group, MemberList, MemberOnList, Klettertreff, class MemberAdmin(admin.ModelAdmin): fields = ['prename', 'lastname', 'email', 'street', 'town', 'phone_number', 'phone_number_parents', 'birth_date', 'group', 'gets_newsletter', 'comments'] - list_display = ('name', 'street', 'town', 'phone_number', - 'phone_number_parents', 'birth_date', 'gets_newsletter', 'get_group', 'comments') + list_display = ('name', 'birth_date', 'gets_newsletter', 'get_group', 'comments') list_filter = ('group', 'gets_newsletter') + formfield_overrides = { + ManyToManyField: {'widget': forms.CheckboxSelectMultiple} + } class GroupAdmin(admin.ModelAdmin): @@ -37,11 +39,12 @@ class MemberListAdminForm(forms.ModelForm): model = MemberList exclude = ['add_member'] + def __init__(self, *args, **kwargs): super(MemberListAdminForm, self).__init__(*args, **kwargs) - if self.instance.pk: - pass - #self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F') + self.fields['jugendleiter'].queryset = Member.objects.filter(group__name='Jugendleiter') + #self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F') + class MemberOnListInline(admin.StackedInline): model = MemberOnList @@ -53,9 +56,13 @@ class MemberOnListInline(admin.StackedInline): } class MemberListAdmin(admin.ModelAdmin): + inlines = [MemberOnListInline] form = MemberListAdminForm + list_display = ['__str__', 'date'] actions = ['convert_to_pdf'] - inlines = [MemberOnListInline] + formfield_overrides = { + ManyToManyField: {'widget': forms.CheckboxSelectMultiple} + } def __init__(self, *args, **kwargs): super(MemberListAdmin, self).__init__(*args, **kwargs) @@ -65,10 +72,9 @@ class MemberListAdmin(admin.ModelAdmin): """ for memberlist in queryset: - # build a unique filename + # create a unique filename filename = memberlist.name + "_" + datetime.today().strftime("%d_%m_%Y") filename = filename.replace(' ', '_') - filename_table = 'table_' + filename filename_tex = filename + '.tex' filename_pdf = filename + '.pdf' @@ -77,9 +83,9 @@ class MemberListAdmin(admin.ModelAdmin): with open('media/memberlists/'+filename_table, 'w+') as f: for memberonlist in memberlist.memberonlist_set.all(): # write table of members in latex compatible format - line = '{0} & {1} & {2} & {3} \\\\ \n'.format(memberonlist.member.prename, - memberonlist.member.lastname, - memberonlist.member.birth_date.strftime('%d.%m.%Y'), memberonlist.comments) + line = '{0} {1} & {2}, {3} & {4} & {5} \\\\ \n'.format(memberonlist.member.prename, + memberonlist.member.lastname, memberonlist.member.street, + memberonlist.member.town, memberonlist.member.phone_number, memberonlist.member.email) f.write(line) # copy and adapt latex memberlist template @@ -91,13 +97,36 @@ class MemberListAdmin(admin.ModelAdmin): template_content = f.read() # adapt template - template_content = template_content.replace('MEMBERLIST-TITLE', memberlist.name) + template_content = template_content.replace('ACTIVITY', memberlist.name) + groups = ', '.join(g.name for g in memberlist.groups.all()) + template_content = template_content.replace('GROUP', groups) + template_content = template_content.replace('DESTINATION', memberlist.destination) + template_content = template_content.replace('PLACE', memberlist.place) template_content = template_content.replace('MEMBERLIST-DATE', - memberlist.date.strftime('%d.%m.%Y')) - template_content = template_content.replace('MEMBERLIST-COMMENTS', - memberlist.comment) + datetime.today().strftime('%d.%m.%Y')) + time_period = memberlist.date.strftime('%d.%m.%Y') + if memberlist.end != memberlist.date: + time_period += " - " + memberlist.end.strftime('%d.%m.%Y') + template_content = template_content.replace('TIME-PERIOD', time_period) + jugendleiter = ', '.join(j.name for j in memberlist.jugendleiter.all()) + template_content = template_content.replace('JUGENDLEITER', jugendleiter) + + # create tickboxes for tour type + tour_type = '' + for tt in ['Gemeinschaftstour', 'Führungstour', 'Ausbildung']: + print(memberlist.tour_type) + if tt in memberlist.tour_type: + tour_type += '\\tickedbox ' + tt + else: + tour_type += '\\checkbox' + tour_type += '\\enspace ' + tt + + tour_type += '\\qquad \\qquad ' + template_content = template_content.replace('TOUR-TYPE', tour_type) + template_content = template_content.replace('TABLE-NAME', filename_table) + # write adapted template to file with open('media/memberlists/' + filename_tex, 'w') as f: f.write(template_content) @@ -126,6 +155,7 @@ class MemberListAdmin(admin.ModelAdmin): return response + class KlettertreffAdminForm(forms.ModelForm): class Meta: model = Klettertreff @@ -178,6 +208,10 @@ class KlettertreffAdmin(admin.ModelAdmin): return render(request, 'admin/klettertreff_overview.html', context) + formfield_overrides = { + ManyToManyField: {'widget': forms.CheckboxSelectMultiple} + } + admin.site.register(Member, MemberAdmin) admin.site.register(Group, GroupAdmin) diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po index b845483..bc9da6d 100644 --- a/jdav_web/members/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-10-21 23:37+0200\n" +"POT-Creation-Date: 2017-01-14 17:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,38 +18,119 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:7 models.py:45 +#: members/apps.py:7 members/models.py:81 msgid "members" msgstr "Teilnehmer" -#: models.py:10 +#: members/models.py:13 msgid "name" msgstr "Name" -#: models.py:12 +#: members/models.py:15 msgid "minimum age (years)" msgstr "Mindestalter (Jahre)" -#: models.py:19 +#: members/models.py:22 members/models.py:39 msgid "group" msgstr "Gruppe" -#: models.py:20 +#: members/models.py:23 msgid "groups" msgstr "Gruppen" -#: models.py:28 +#: members/models.py:31 msgid "prename" msgstr "Vorname" -#: models.py:29 +#: members/models.py:32 msgid "last name" msgstr "Nachname" -#: models.py:31 +#: members/models.py:33 +msgid "street" +msgstr "Straße" + +#: members/models.py:34 +msgid "town" +msgstr "Stadt" + +#: members/models.py:35 +msgid "phone number" +msgstr "Telefonnummer" + +#: members/models.py:36 +msgid "parents phone number" +msgstr "Telefonnummer der Eltern" + +#: members/models.py:38 msgid "birth date" msgstr "Geburtsdatum" -#: models.py:44 +#: members/models.py:40 +msgid "receives newsletter" +msgstr "Erhält den Newsletter" + +#: members/models.py:44 +msgid "comments" +msgstr "Kommentare" + +#: members/models.py:77 +msgid "Group" +msgstr "Gruppe" + +#: members/models.py:80 msgid "member" msgstr "Teilnehmer" + +#: members/models.py:86 +msgid "Listname" +msgstr "Name der Liste" + +#: members/models.py:88 +msgid "date" +msgstr "Datum" + +#: members/models.py:89 +msgid "Comments" +msgstr "Kommentare" + +#: members/models.py:96 +msgid "Memberlist" +msgstr "Teilnehmerliste" + +#: members/models.py:97 +msgid "Memberlists" +msgstr "Teilnehmerlisten" + +#: members/models.py:104 members/models.py:109 members/models.py:139 +#: members/models.py:143 +msgid "Member" +msgstr "Teilnehmer" + +#: members/models.py:106 +msgid "Comment" +msgstr "Kommentar" + +#: members/models.py:110 members/models.py:144 +msgid "Members" +msgstr "Teilnehmer" + +#: members/models.py:119 +msgid "Date" +msgstr "Datum" + +#: members/models.py:120 +msgid "Location" +msgstr "Ort" + +#: members/models.py:130 +msgid "Jugendleiter" +msgstr "Jugendleiter" + +#: members/models.py:133 +msgid "Klettertreff" +msgstr "Klettertreff" + +#: members/models.py:134 +msgid "Klettertreffs" +msgstr "Klettertreffs" diff --git a/jdav_web/members/migrations/0001_initial.py b/jdav_web/members/migrations/0001_initial.py index 3c010b2..990e9ef 100644 --- a/jdav_web/members/migrations/0001_initial.py +++ b/jdav_web/members/migrations/0001_initial.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.2 on 2016-10-18 17:36 +# Generated by Django 1.10.2 on 2016-11-19 14:56 from __future__ import unicode_literals +import datetime from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -18,18 +18,36 @@ class Migration(migrations.Migration): name='Group', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=20)), - ('min_age', models.IntegerField(default=5)), + ('name', models.CharField(max_length=20, verbose_name='name')), + ('min_age', models.IntegerField(default=5, verbose_name='minimum age (years)')), ], + options={ + 'verbose_name_plural': 'groups', + 'verbose_name': 'group', + }, ), migrations.CreateModel( name='Member', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('prename', models.CharField(max_length=20)), - ('lastname', models.CharField(max_length=20)), + ('prename', models.CharField(max_length=20, verbose_name='prename')), + ('lastname', models.CharField(max_length=20, verbose_name='last name')), + ('email', models.EmailField(default='', max_length=100)), ('birth_date', models.DateField(verbose_name='birth date')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.Group')), + ('group', models.ManyToManyField(to='members.Group')), + ], + options={ + 'verbose_name_plural': 'members', + 'verbose_name': 'member', + }, + ), + migrations.CreateModel( + name='MemberList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='', max_length=50, verbose_name='List Name')), + ('date', models.DateField(default=datetime.datetime.today)), + ('add_member', models.ManyToManyField(to='members.Member')), ], ), ] diff --git a/jdav_web/members/migrations/0002_member_email.py b/jdav_web/members/migrations/0002_member_email.py deleted file mode 100644 index a95ffbc..0000000 --- a/jdav_web/members/migrations/0002_member_email.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.2 on 2016-10-18 17:41 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='member', - name='email', - field=models.CharField(default='', max_length=100), - ), - ] diff --git a/jdav_web/members/migrations/0003_auto_20161018_1742.py b/jdav_web/members/migrations/0003_auto_20161018_1742.py deleted file mode 100644 index f95b120..0000000 --- a/jdav_web/members/migrations/0003_auto_20161018_1742.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.2 on 2016-10-18 17:42 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0002_member_email'), - ] - - operations = [ - migrations.AlterField( - model_name='member', - name='email', - field=models.EmailField(default='', max_length=100), - ), - ] diff --git a/jdav_web/members/migrations/0004_auto_20161018_1744.py b/jdav_web/members/migrations/0004_auto_20161018_1744.py deleted file mode 100644 index e479a16..0000000 --- a/jdav_web/members/migrations/0004_auto_20161018_1744.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.2 on 2016-10-18 17:44 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0003_auto_20161018_1742'), - ] - - operations = [ - migrations.RemoveField( - model_name='member', - name='group', - ), - migrations.AddField( - model_name='member', - name='group', - field=models.ManyToManyField(to='members.Group'), - ), - ] diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py index 621004f..d6f664a 100644 --- a/jdav_web/members/models.py +++ b/jdav_web/members/models.py @@ -1,9 +1,11 @@ from datetime import datetime import uuid +from django import forms from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone +from multiselectfield import MultiSelectField class Group(models.Model): """ @@ -36,7 +38,7 @@ class Member(models.Model): phone_number_parents = models.CharField(max_length=12, verbose_name=_('parents phone number'), default='', blank=True) email = models.EmailField(max_length=100, default="") birth_date = models.DateField(_('birth date')) # to determine the age - group = models.ManyToManyField(Group) + group = models.ManyToManyField(Group, verbose_name=_('group')) gets_newsletter = models.BooleanField(_('receives newsletter'), default=True) unsubscribe_key = models.CharField(max_length=32, default="") @@ -68,7 +70,7 @@ class Member(models.Model): def name(self): """Returning whole name (prename + lastname)""" return "{0} {1}".format(self.prename, self.lastname) - + def get_group(self): """Returns a string of groups in which the member is.""" groupstring = ''.join(g.name + ',\n' for g in self.group.all()) @@ -83,30 +85,48 @@ class Member(models.Model): class MemberList(models.Model): """Lets the user create a list of members in pdf format. """ - name = models.CharField(verbose_name='List Name', default='', + + name = models.CharField(verbose_name='Activity', default='', max_length=50) + place = models.CharField(verbose_name=_('Place'), default='', max_length=50) + destination = models.CharField(verbose_name=_('Destination (optional)'), default='', max_length=50, blank=True) date = models.DateField(default=datetime.today) - comment = models.TextField(_('Comments'), default='') + end = models.DateField(verbose_name=_('End (optional)'), blank=True, default=datetime.today) + #comment = models.TextField(_('Comments'), default='', blank=True) + groups = models.ManyToManyField(Group) + jugendleiter = models.ManyToManyField(Member) + tour_type_choices = (('Gemeinschaftstour','Gemeinschaftstour'), ('Führungstour', 'Führungstour'), + ('Ausbildung', 'Ausbildung')) + tour_type = MultiSelectField(choices=tour_type_choices, default='', max_choices=1) + def __str__(self): """String represenation""" return self.name + class Meta: + verbose_name = _('Memberlist') + verbose_name_plural = _('Memberlists') + class MemberOnList(models.Model): """ Connects members to a list of members. """ - member = models.ForeignKey(Member) + member = models.ForeignKey(Member, verbose_name=_('Member')) memberlist = models.ForeignKey(MemberList) - comments = models.TextField(_('Comment'), default='') + comments = models.TextField(_('Comment'), default='', blank=True) + + class Meta: + verbose_name = _('Member') + verbose_name_plural = _('Members') class Klettertreff(models.Model): """ This model represents a Klettertreff event. - A Klettertreff can take a date, location, Jugendleiter, attending members as - input. + A Klettertreff can take a date, location, Jugendleiter, attending members + as input. """ date = models.DateField(_('Date'), default=datetime.today) location = models.CharField(_('Location'), default='', max_length=60) @@ -117,8 +137,7 @@ class Klettertreff(models.Model): return self.location + ' ' + self.date.strftime('%d.%m.%Y') def get_jugendleiter(self): - jl_string = ''.join(j.name + ',\n' for j in self.jugendleiter.all()) - jl_string = jl_string[:-2] + jl_string = ', '.join(j.name for j in self.jugendleiter.all()) return jl_string def has_attendee(self, member): @@ -137,9 +156,16 @@ class Klettertreff(models.Model): get_jugendleiter.short_description = _('Jugendleiter') + class Meta: + verbose_name = _('Klettertreff') + verbose_name_plural = _('Klettertreffs') + class KlettertreffAttendee(models.Model): """Connects members to Klettertreffs.""" - member = models.ForeignKey(Member) + member = models.ForeignKey(Member, verbose_name=_('Member')) klettertreff = models.ForeignKey(Klettertreff) + class Meta: + verbose_name = _('Member') + verbose_name_plural = _('Members') diff --git a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po index 0973fdc..2fb98ed 100644 --- a/jdav_web/startpage/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/startpage/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-10-22 00:04+0200\n" +"POT-Creation-Date: 2017-01-14 17:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,6 +18,6 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: templates/startpage/index.html:2 +#: startpage/templates/startpage/index.html:2 msgid "Awesome JDAV website being able to do a lot!" msgstr "Tolle JDAV Webseite die ganz viel kann!" diff --git a/jdav_web/startpage/migrations/__init__.py b/jdav_web/startpage/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/jdav_web/static/admin/css/advanced.css b/jdav_web/static/admin/css/advanced.css new file mode 100644 index 0000000..9cbc2a8 --- /dev/null +++ b/jdav_web/static/admin/css/advanced.css @@ -0,0 +1,120 @@ +/* This is the extension of the base.css file. If you need to change anything in the base.css + * copy the extract inside this file and do your changes. + * + * To change the global colors, edit the colors.css file */ +@import url('colors.css'); + +a:link, a:visited { + color: var(--link); + text-decoration: none; +} + +a:focus, a:hover { + color: var(--link-hover); +} + +#user-tools a { + border-bottom: 1px solid rgba(255, 255, 255, 0.25); +} + +#user-tools a:focus, #user-tools a:hover { + text-decoration: none; + border-bottom-color: var(--link-hover); + color: var(--link-hover); +} + +/* HEADER */ + +#header { + width: auto; + height: 40px; + padding: 10px 40px; + background: var(--primary); + line-height: 40px; + color: #ffc; + overflow: hidden; +} + +#branding h1 { + padding: 0; + margin: 0 20px 0 0; + font-weight: 300; + font-size: 24px; + color: var(--text-light); +} +#branding h1, #branding h1 a:link, #branding h1 a:visited { + color: var(--text-light); +} + +#logo { + float: left; + margin: 0 20px 0 0; + display: block; + width: 128px; + height: auto; +} + +.module h2, .module caption, .inline-group h2 { + margin: 0; + padding: 8px; + font-weight: 400; + font-size: 13px; + text-align: left; + background: var(--primary-dark); + color: var(--text-light); +} + +/* FORM BUTTONS */ + +.button, input[type=submit], input[type=button], .submit-row input, a.button { + background: var(--primary-dark); + padding: 10px 15px; + border: none; + border-radius: 4px; + color: var(--text-light); + cursor: pointer; +} + +.button:active, input[type=submit]:active, input[type=button]:active, +.button:focus, input[type=submit]:focus, input[type=button]:focus, +.button:hover, input[type=submit]:hover, input[type=button]:hover { + background: var(--primary-dark); +} + +.button.default, input[type=submit].default, .submit-row input.default { + float: right; + border: none; + font-weight: 400; + background: var(--primary-dark); +} + +.button.default:active, input[type=submit].default:active, +.button.default:focus, input[type=submit].default:focus, +.button.default:hover, input[type=submit].default:hover { + background: var(--primary-dark); +} + +/* BREADCRUMBS */ + +div.breadcrumbs { + background: var(--primary-dark); + padding: 10px 40px; + border: none; + font-size: 14px; + color: var(--link-dark); + text-align: left; +} + +div.breadcrumbs a { + color: var(--text-light); +} + +div.breadcrumbs a:focus, div.breadcrumbs a:hover { + color: var(--link-hover); +} + +/* OBJECT TOOLS */ + +.object-tools a:focus, .object-tools a:hover { + background-color: var(--primary-dark); +} diff --git a/jdav_web/static/admin/css/base.css b/jdav_web/static/admin/css/base.css new file mode 100644 index 0000000..1aaa100 --- /dev/null +++ b/jdav_web/static/admin/css/base.css @@ -0,0 +1,971 @@ +/* + DJANGO Admin styles +*/ + +@import url(fonts.css); + +body { + margin: 0; + padding: 0; + font-size: 14px; + font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; + color: #333; + background: #fff; +} + +/* LINKS */ + +a:link, a:visited { + color: #447e9b; + text-decoration: none; +} + +a:focus, a:hover { + color: #036; +} + +a:focus { + text-decoration: underline; +} + +a img { + border: none; +} + +a.section:link, a.section:visited { + color: #fff; + text-decoration: none; +} + +a.section:focus, a.section:hover { + text-decoration: underline; +} + +/* GLOBAL DEFAULTS */ + +p, ol, ul, dl { + margin: .2em 0 .8em 0; +} + +p { + padding: 0; + line-height: 140%; +} + +h1,h2,h3,h4,h5 { + font-weight: bold; +} + +h1 { + margin: 0 0 20px; + font-weight: 300; + font-size: 20px; + color: #666; +} + +h2 { + font-size: 16px; + margin: 1em 0 .5em 0; +} + +h2.subhead { + font-weight: normal; + margin-top: 0; +} + +h3 { + font-size: 14px; + margin: .8em 0 .3em 0; + color: #666; + font-weight: bold; +} + +h4 { + font-size: 12px; + margin: 1em 0 .8em 0; + padding-bottom: 3px; +} + +h5 { + font-size: 10px; + margin: 1.5em 0 .5em 0; + color: #666; + text-transform: uppercase; + letter-spacing: 1px; +} + +ul li { + list-style-type: square; + padding: 1px 0; +} + +li ul { + margin-bottom: 0; +} + +li, dt, dd { + font-size: 13px; + line-height: 20px; +} + +dt { + font-weight: bold; + margin-top: 4px; +} + +dd { + margin-left: 0; +} + +form { + margin: 0; + padding: 0; +} + +fieldset { + margin: 0; + padding: 0; + border: none; + border-top: 1px solid #eee; +} + +blockquote { + font-size: 11px; + color: #777; + margin-left: 2px; + padding-left: 10px; + border-left: 5px solid #ddd; +} + +code, pre { + font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; + color: #666; + font-size: 12px; +} + +pre.literal-block { + margin: 10px; + background: #eee; + padding: 6px 8px; +} + +code strong { + color: #930; +} + +hr { + clear: both; + color: #eee; + background-color: #eee; + height: 1px; + border: none; + margin: 0; + padding: 0; + font-size: 1px; + line-height: 1px; +} + +/* TEXT STYLES & MODIFIERS */ + +.small { + font-size: 11px; +} + +.tiny { + font-size: 10px; +} + +p.tiny { + margin-top: -2px; +} + +.mini { + font-size: 10px; +} + +p.mini { + margin-top: -3px; +} + +.help, p.help, form p.help { + font-size: 11px; + color: #999; +} + +.help-tooltip { + cursor: help; +} + +p img, h1 img, h2 img, h3 img, h4 img, td img { + vertical-align: middle; +} + +.quiet, a.quiet:link, a.quiet:visited { + color: #999; + font-weight: normal; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.clear { + clear: both; +} + +.align-left { + text-align: left; +} + +.align-right { + text-align: right; +} + +.example { + margin: 10px 0; + padding: 5px 10px; + background: #efefef; +} + +.nowrap { + white-space: nowrap; +} + +/* TABLES */ + +table { + border-collapse: collapse; + border-color: #ccc; +} + +td, th { + font-size: 13px; + line-height: 16px; + border-bottom: 1px solid #eee; + vertical-align: top; + padding: 8px; + font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif; +} + +th { + font-weight: 600; + text-align: left; +} + +thead th, +tfoot td { + color: #666; + padding: 5px 10px; + font-size: 11px; + background: #fff; + border: none; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} + +tfoot td { + border-bottom: none; + border-top: 1px solid #eee; +} + +thead th.required { + color: #000; +} + +tr.alt { + background: #f6f6f6; +} + +.row1 { + background: #fff; +} + +.row2 { + background: #f9f9f9; +} + +/* SORTABLE TABLES */ + +thead th { + padding: 5px 10px; + line-height: normal; + text-transform: uppercase; + background: #f6f6f6; +} + +thead th a:link, thead th a:visited { + color: #666; +} + +thead th.sorted { + background: #eee; +} + +thead th.sorted .text { + padding-right: 42px; +} + +table thead th .text span { + padding: 8px 10px; + display: block; +} + +table thead th .text a { + display: block; + cursor: pointer; + padding: 8px 10px; +} + +table thead th .text a:focus, table thead th .text a:hover { + background: #eee; +} + +thead th.sorted a.sortremove { + visibility: hidden; +} + +table thead th.sorted:hover a.sortremove { + visibility: visible; +} + +table thead th.sorted .sortoptions { + display: block; + padding: 9px 5px 0 5px; + float: right; + text-align: right; +} + +table thead th.sorted .sortpriority { + font-size: .8em; + min-width: 12px; + text-align: center; + vertical-align: 3px; + margin-left: 2px; + margin-right: 2px; +} + +table thead th.sorted .sortoptions a { + position: relative; + width: 14px; + height: 14px; + display: inline-block; + background: url(../img/sorting-icons.svg) 0 0 no-repeat; + background-size: 14px auto; +} + +table thead th.sorted .sortoptions a.sortremove { + background-position: 0 0; +} + +table thead th.sorted .sortoptions a.sortremove:after { + content: '\\'; + position: absolute; + top: -6px; + left: 3px; + font-weight: 200; + font-size: 18px; + color: #999; +} + +table thead th.sorted .sortoptions a.sortremove:focus:after, +table thead th.sorted .sortoptions a.sortremove:hover:after { + color: #447e9b; +} + +table thead th.sorted .sortoptions a.sortremove:focus, +table thead th.sorted .sortoptions a.sortremove:hover { + background-position: 0 -14px; +} + +table thead th.sorted .sortoptions a.ascending { + background-position: 0 -28px; +} + +table thead th.sorted .sortoptions a.ascending:focus, +table thead th.sorted .sortoptions a.ascending:hover { + background-position: 0 -42px; +} + +table thead th.sorted .sortoptions a.descending { + top: 1px; + background-position: 0 -56px; +} + +table thead th.sorted .sortoptions a.descending:focus, +table thead th.sorted .sortoptions a.descending:hover { + background-position: 0 -70px; +} + +/* FORM DEFAULTS */ + +input, textarea, select, .form-row p, form .button { + margin: 2px 0; + padding: 2px 3px; + vertical-align: middle; + font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif; + font-weight: normal; + font-size: 13px; +} + +textarea { + vertical-align: top; +} + +input[type=text], input[type=password], input[type=email], input[type=url], +input[type=number], textarea, select, .vTextField { + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px 6px; + margin-top: 0; +} + +input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, +input[type=url]:focus, input[type=number]:focus, textarea:focus, select:focus, +.vTextField:focus { + border-color: #999; +} + +select { + height: 30px; +} + +select[multiple] { + min-height: 150px; +} + +/* FORM BUTTONS */ + +.button, input[type=submit], input[type=button], .submit-row input, a.button { + background: #000; + padding: 10px 15px; + border: none; + border-radius: 4px; + color: #fff; + cursor: pointer; +} + +a.button { + padding: 4px 5px; +} + +.button:active, input[type=submit]:active, input[type=button]:active, +.button:focus, input[type=submit]:focus, input[type=button]:focus, +.button:hover, input[type=submit]:hover, input[type=button]:hover { + background: #609ab6; +} + +.button[disabled], input[type=submit][disabled], input[type=button][disabled] { + opacity: 0.4; +} + +.button.default, input[type=submit].default, .submit-row input.default { + float: right; + border: none; + font-weight: 400; + background: #417690; +} + +.button.default:active, input[type=submit].default:active, +.button.default:focus, input[type=submit].default:focus, +.button.default:hover, input[type=submit].default:hover { + background: #205067; +} + +.button[disabled].default, +input[type=submit][disabled].default, +input[type=button][disabled].default { + opacity: 0.4; +} + + +/* MODULES */ + +.module { + border: none; + margin-bottom: 30px; + background: #fff; +} + +.module p, .module ul, .module h3, .module h4, .module dl, .module pre { + padding-left: 10px; + padding-right: 10px; +} + +.module blockquote { + margin-left: 12px; +} + +.module ul, .module ol { + margin-left: 1.5em; +} + +.module h3 { + margin-top: .6em; +} + +.module h2, .module caption, .inline-group h2 { + margin: 0; + padding: 8px; + font-weight: 400; + font-size: 13px; + text-align: left; + background: #000; + color: #fff; +} + +.module caption, +.inline-group h2 { + font-size: 12px; + letter-spacing: 0.5px; + text-transform: uppercase; +} + +.module table { + border-collapse: collapse; +} + +/* MESSAGES & ERRORS */ + +ul.messagelist { + padding: 0; + margin: 0; +} + +ul.messagelist li { + display: block; + font-weight: 400; + font-size: 13px; + padding: 10px 10px 10px 65px; + margin: 0 0 10px 0; + background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat; + background-size: 16px auto; + color: #333; +} + +ul.messagelist li.warning { + background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat; + background-size: 14px auto; +} + +ul.messagelist li.error { + background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat; + background-size: 16px auto; +} + +.errornote { + font-size: 14px; + font-weight: 700; + display: block; + padding: 10px 12px; + margin: 0 0 10px 0; + color: #ba2121; + border: 1px solid #ba2121; + border-radius: 4px; + background-color: #fff; + background-position: 5px 12px; +} + +ul.errorlist { + margin: 0 0 4px; + padding: 0; + color: #ba2121; + background: #fff; +} + +ul.errorlist li { + font-size: 13px; + display: block; + margin-bottom: 4px; +} + +ul.errorlist li:first-child { + margin-top: 0; +} + +ul.errorlist li a { + color: inherit; + text-decoration: underline; +} + +td ul.errorlist { + margin: 0; + padding: 0; +} + +td ul.errorlist li { + margin: 0; +} + +.form-row.errors { + margin: 0; + border: none; + border-bottom: 1px solid #eee; + background: none; +} + +.form-row.errors ul.errorlist li { + padding-left: 0; +} + +.errors input, .errors select, .errors textarea { + border: 1px solid #ba2121; +} + +div.system-message { + background: #ffc; + margin: 10px; + padding: 6px 8px; + font-size: .8em; +} + +div.system-message p.system-message-title { + padding: 4px 5px 4px 25px; + margin: 0; + color: #c11; + background: #ffefef url(../img/icon-no.svg) 5px 5px no-repeat; +} + +.description { + font-size: 12px; + padding: 5px 0 0 12px; +} + +/* BREADCRUMBS */ + +div.breadcrumbs { + background: #000; + padding: 10px 40px; + border: none; + font-size: 14px; + color: #c4dce8; + text-align: left; +} + +div.breadcrumbs a { + color: #fff; +} + +div.breadcrumbs a:focus, div.breadcrumbs a:hover { + color: #c4dce8; +} + +/* ACTION ICONS */ + +.addlink { + padding-left: 16px; + background: url(../img/icon-addlink.svg) 0 1px no-repeat; +} + +.changelink, .inlinechangelink { + padding-left: 16px; + background: url(../img/icon-changelink.svg) 0 1px no-repeat; +} + +.deletelink { + padding-left: 16px; + background: url(../img/icon-deletelink.svg) 0 1px no-repeat; +} + +a.deletelink:link, a.deletelink:visited { + color: #CC3434; +} + +a.deletelink:focus, a.deletelink:hover { + color: #993333; + text-decoration: none; +} + +/* OBJECT TOOLS */ + +.object-tools { + font-size: 10px; + font-weight: bold; + padding-left: 0; + float: right; + position: relative; + margin-top: -48px; +} + +.form-row .object-tools { + margin-top: 5px; + margin-bottom: 5px; + float: none; + height: 2em; + padding-left: 3.5em; +} + +.object-tools li { + display: block; + float: left; + margin-left: 5px; + height: 16px; +} + +.object-tools a { + border-radius: 15px; +} + +.object-tools a:link, .object-tools a:visited { + display: block; + float: left; + padding: 3px 12px; + background: #999; + font-weight: 400; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #fff; +} + +.object-tools a:focus, .object-tools a:hover { + background-color: #417690; +} + +.object-tools a:focus{ + text-decoration: none; +} + +.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink { + background-repeat: no-repeat; + background-position: 93% center; + padding-right: 26px; +} + +.object-tools a.viewsitelink, .object-tools a.golink { + background-image: url(../img/tooltag-arrowright.svg); +} + +.object-tools a.addlink { + background-image: url(../img/tooltag-add.svg); +} + +/* OBJECT HISTORY */ + +table#change-history { + width: 100%; +} + +table#change-history tbody th { + width: 16em; +} + +/* PAGE STRUCTURE */ + +#container { + position: relative; + width: 100%; + min-width: 980px; + padding: 0; +} + +#content { + padding: 20px 40px; +} + +.dashboard #content { + width: 600px; +} + +#content-main { + float: left; + width: 100%; +} + +#content-related { + float: right; + width: 260px; + position: relative; + margin-right: -300px; +} + +#footer { + clear: both; + padding: 10px; +} + +/* COLUMN TYPES */ + +.colMS { + margin-right: 300px; +} + +.colSM { + margin-left: 300px; +} + +.colSM #content-related { + float: left; + margin-right: 0; + margin-left: -300px; +} + +.colSM #content-main { + float: right; +} + +.popup .colM { + width: auto; +} + +/* HEADER */ + +#header { + width: auto; + height: 40px; + padding: 10px 40px; + background: #417690; + line-height: 40px; + color: #ffc; + overflow: hidden; +} + +#header a:link, #header a:visited { + color: #fff; +} + +#header a:focus , #header a:hover { + text-decoration: underline; +} + +#branding { + float: left; +} + +#branding h1 { + padding: 0; + margin: 0 20px 0 0; + font-weight: 300; + font-size: 24px; + color: #f5dd5d; +} + +#branding h1, #branding h1 a:link, #branding h1 a:visited { + color: #f5dd5d; +} + +#branding h2 { + padding: 0 10px; + font-size: 14px; + margin: -8px 0 8px 0; + font-weight: normal; + color: #ffc; +} + +#branding a:hover { + text-decoration: none; +} + +#user-tools { + float: right; + padding: 0; + margin: 0 0 0 20px; + font-weight: 300; + font-size: 11px; + letter-spacing: 0.5px; + text-transform: uppercase; + text-align: right; +} + +#user-tools a { + border-bottom: 1px solid rgba(255, 255, 255, 0.25); +} + +#user-tools a:focus, #user-tools a:hover { + text-decoration: none; + border-bottom-color: #000; + color: #000; +} + +/* SIDEBAR */ + +#content-related { + background: #f8f8f8; +} + +#content-related .module { + background: none; +} + +#content-related h3 { + font-size: 14px; + color: #666; + padding: 0 16px; + margin: 0 0 16px; +} + +#content-related h4 { + font-size: 13px; +} + +#content-related p { + padding-left: 16px; + padding-right: 16px; +} + +#content-related .actionlist { + padding: 0; + margin: 16px; +} + +#content-related .actionlist li { + line-height: 1.2; + margin-bottom: 10px; + padding-left: 18px; +} + +#content-related .module h2 { + background: none; + padding: 16px; + margin-bottom: 16px; + border-bottom: 1px solid #eaeaea; + font-size: 18px; + color: #333; +} + +.delete-confirmation form input[type="submit"] { + background: #ba2121; + border-radius: 4px; + padding: 10px 15px; + color: #fff; +} + +.delete-confirmation form input[type="submit"]:active, +.delete-confirmation form input[type="submit"]:focus, +.delete-confirmation form input[type="submit"]:hover { + background: #a41515; +} + +.delete-confirmation form .cancel-link { + display: inline-block; + vertical-align: middle; + height: 15px; + line-height: 15px; + background: #ddd; + border-radius: 4px; + padding: 10px 15px; + color: #333; + margin: 0 0 0 10px; +} + +.delete-confirmation form .cancel-link:active, +.delete-confirmation form .cancel-link:focus, +.delete-confirmation form .cancel-link:hover { + background: #ccc; +} + +/* POPUP */ +.popup #content { + padding: 20px; +} + +.popup #container { + min-width: 0; +} + +.popup #header { + padding: 10px 20px; +} diff --git a/jdav_web/static/admin/css/changelists.css b/jdav_web/static/admin/css/changelists.css new file mode 100644 index 0000000..8b1522f --- /dev/null +++ b/jdav_web/static/admin/css/changelists.css @@ -0,0 +1,351 @@ +:root { + --primary: #5fad00; + --primary-dark: #6dad1e; + --link: #666; + --link-dark: #222; + --link-hover: #444; + --text-light: #fff; +} + +/* CHANGELISTS */ + +#changelist { + position: relative; + width: 100%; +} + +#changelist table { + width: 100%; +} + +.change-list .hiddenfields { display:none; } + +.change-list .filtered table { + border-right: none; +} + +.change-list .filtered { + min-height: 400px; +} + +.change-list .filtered .results, .change-list .filtered .paginator, +.filtered #toolbar, .filtered div.xfull { + margin-right: 280px; + width: auto; +} + +.change-list .filtered table tbody th { + padding-right: 1em; +} + +#changelist-form .results { + overflow-x: auto; +} + +#changelist .toplinks { + border-bottom: 1px solid #ddd; +} + +#changelist .paginator { + color: #666; + border-bottom: 1px solid #eee; + background: #fff; + overflow: hidden; +} + +/* CHANGELIST TABLES */ + +#changelist table thead th { + padding: 0; + white-space: nowrap; + vertical-align: middle; +} + +#changelist table thead th.action-checkbox-column { + width: 1.5em; + text-align: center; +} + +#changelist table tbody td.action-checkbox { + text-align: center; +} + +#changelist table tfoot { + color: #666; +} + +/* TOOLBAR */ + +#changelist #toolbar { + padding: 8px 10px; + margin-bottom: 15px; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + background: #f8f8f8; + color: #666; +} + +#changelist #toolbar form input { + border-radius: 4px; + font-size: 14px; + padding: 5px; + color: #333; +} + +#changelist #toolbar form #searchbar { + height: 19px; + border: 1px solid #ccc; + padding: 2px 5px; + margin: 0; + vertical-align: top; + font-size: 13px; +} + +#changelist #toolbar form #searchbar:focus { + border-color: #999; +} + +#changelist #toolbar form input[type="submit"] { + border: 1px solid #ccc; + padding: 2px 10px; + margin: 0; + vertical-align: middle; + background: #fff; + box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; + cursor: pointer; + color: #333; +} + +#changelist #toolbar form input[type="submit"]:focus, +#changelist #toolbar form input[type="submit"]:hover { + border-color: #999; +} + +#changelist #changelist-search img { + vertical-align: middle; + margin-right: 4px; +} + +/* FILTER COLUMN */ + +#changelist-filter { + position: absolute; + top: 0; + right: 0; + z-index: 1000; + width: 240px; + background: #f8f8f8; + border-left: none; + margin: 0; +} + +#changelist-filter h2 { + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 5px 15px; + margin-bottom: 12px; + border-bottom: none; +} + +#changelist-filter h3 { + font-weight: 400; + font-size: 14px; + padding: 0 15px; + margin-bottom: 10px; +} + +#changelist-filter ul { + margin: 5px 0; + padding: 0 15px 15px; + border-bottom: 1px solid #eaeaea; +} + +#changelist-filter ul:last-child { + border-bottom: none; + padding-bottom: none; +} + +#changelist-filter li { + list-style-type: none; + margin-left: 0; + padding-left: 0; +} + +#changelist-filter a { + display: block; + color: #999; +} + +#changelist-filter li.selected { + border-left: 5px solid #eaeaea; + padding-left: 10px; + margin-left: -15px; +} + +#changelist-filter li.selected a { + color: #5b80b2; +} + +#changelist-filter a:focus, #changelist-filter a:hover, +#changelist-filter li.selected a:focus, +#changelist-filter li.selected a:hover { + color: #036; +} + +/* DATE DRILLDOWN */ + +.change-list ul.toplinks { + display: block; + float: left; + padding: 0; + margin: 0; + width: 100%; +} + +.change-list ul.toplinks li { + padding: 3px 6px; + font-weight: bold; + list-style-type: none; + display: inline-block; +} + +.change-list ul.toplinks .date-back a { + color: #999; +} + +.change-list ul.toplinks .date-back a:focus, +.change-list ul.toplinks .date-back a:hover { + color: #036; +} + +/* PAGINATOR */ + +.paginator { + font-size: 13px; + padding-top: 10px; + padding-bottom: 10px; + line-height: 22px; + margin: 0; + border-top: 1px solid #ddd; +} + +.paginator a:link, .paginator a:visited { + padding: 2px 6px; + background: #000; + text-decoration: none; + color: #fff; +} + +.paginator a.showall { + padding: 0; + border: none; + background: none; + color: #5b80b2; +} + +.paginator a.showall:focus, .paginator a.showall:hover { + background: none; + color: #036; +} + +.paginator .end { + margin-right: 6px; +} + +.paginator .this-page { + padding: 2px 6px; + font-weight: bold; + font-size: 13px; + vertical-align: top; +} + +.paginator a:focus, .paginator a:hover { + color: white; + background: #036; +} + +/* ACTIONS */ + +.filtered .actions { + margin-right: 280px; + border-right: none; +} + +#changelist table input { + margin: 0; + vertical-align: baseline; +} + +#changelist table tbody tr.selected { + background-color: #FFFFCC; +} + +#changelist .actions { + padding: 10px; + background: #fff; + border-top: none; + border-bottom: none; + line-height: 24px; + color: #999; +} + +#changelist .actions.selected { + background: #fffccf; + border-top: 1px solid #fffee8; + border-bottom: 1px solid #edecd6; +} + +#changelist .actions span.all, +#changelist .actions span.action-counter, +#changelist .actions span.clear, +#changelist .actions span.question { + font-size: 13px; + margin: 0 0.5em; + display: none; +} + +#changelist .actions:last-child { + border-bottom: none; +} + +#changelist .actions select { + vertical-align: top; + height: 24px; + background: none; + color: #000; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 14px; + padding: 0 0 0 4px; + margin: 0; + margin-left: 10px; +} + +#changelist .actions select:focus { + border-color: #999; +} + +#changelist .actions label { + display: inline-block; + vertical-align: middle; + font-size: 13px; +} + +#changelist .actions .button { + font-size: 13px; + border: 1px solid #ccc; + border-radius: 4px; + background: #fff; + box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; + cursor: pointer; + height: 24px; + line-height: 1; + padding: 4px 8px; + margin: 0; + color: #333; +} + +#changelist .actions .button:focus, #changelist .actions .button:hover { + border-color: #999; +} diff --git a/jdav_web/static/admin/css/colors.css b/jdav_web/static/admin/css/colors.css new file mode 100644 index 0000000..a6d9739 --- /dev/null +++ b/jdav_web/static/admin/css/colors.css @@ -0,0 +1,9 @@ +/* Admin color file. Keep the number of different colors low to increase readability of the webpage */ +:root { + --primary: #5fad00; + --primary-dark: #6dad1e; + --link: #666; + --link-dark: #222; + --link-hover: #444; + --text-light: #fff; +} diff --git a/jdav_web/static/admin/css/widgets.css b/jdav_web/static/admin/css/widgets.css new file mode 100644 index 0000000..d02ac59 --- /dev/null +++ b/jdav_web/static/admin/css/widgets.css @@ -0,0 +1,566 @@ +@import('colors.css'); +/* SELECTOR (FILTER INTERFACE) */ + +.selector { + width: 800px; + float: left; +} + +.selector select { + width: 380px; + height: 17.2em; +} + +.selector-available, .selector-chosen { + float: left; + width: 380px; + text-align: center; + margin-bottom: 5px; +} + +.selector-chosen select { + border-top: none; +} + +.selector-available h2, .selector-chosen h2 { + border: 1px solid #ccc; + border-radius: 4px 4px 0 0; +} + +.selector-chosen h2 { + background: var(--primary-dark); + color: #fff; +} + +.selector .selector-available h2 { + background: #f8f8f8; + color: #666; +} + +.selector .selector-filter { + background: white; + border: 1px solid #ccc; + border-width: 0 1px; + padding: 8px; + color: #999; + font-size: 10px; + margin: 0; + text-align: left; +} + +.selector .selector-filter label, +.inline-group .aligned .selector .selector-filter label { + float: left; + margin: 7px 0 0; + width: 18px; + height: 18px; + padding: 0; + overflow: hidden; + line-height: 1; +} + +.selector .selector-available input { + width: 320px; + margin-left: 8px; +} + +.selector ul.selector-chooser { + float: left; + width: 22px; + background-color: #eee; + border-radius: 10px; + margin: 10em 5px 0 5px; + padding: 0; +} + +.selector-chooser li { + margin: 0; + padding: 3px; + list-style-type: none; +} + +.selector select { + padding: 0 10px; + margin: 0 0 10px; + border-radius: 0 0 4px 4px; +} + +.selector-add, .selector-remove { + width: 16px; + height: 16px; + display: block; + text-indent: -3000px; + overflow: hidden; + cursor: default; + opacity: 0.3; +} + +.active.selector-add, .active.selector-remove { + opacity: 1; +} + +.active.selector-add:hover, .active.selector-remove:hover { + cursor: pointer; +} + +.selector-add { + background: url(../img/selector-icons.svg) 0 -96px no-repeat; +} + +.active.selector-add:focus, .active.selector-add:hover { + background-position: 0 -112px; +} + +.selector-remove { + background: url(../img/selector-icons.svg) 0 -64px no-repeat; +} + +.active.selector-remove:focus, .active.selector-remove:hover { + background-position: 0 -80px; +} + +a.selector-chooseall, a.selector-clearall { + display: inline-block; + height: 16px; + text-align: left; + margin: 1px auto 3px; + overflow: hidden; + font-weight: bold; + line-height: 16px; + color: #666; + text-decoration: none; + opacity: 0.3; +} + +a.active.selector-chooseall:focus, a.active.selector-clearall:focus, +a.active.selector-chooseall:hover, a.active.selector-clearall:hover { + color: #447e9b; +} + +a.active.selector-chooseall, a.active.selector-clearall { + opacity: 1; +} + +a.active.selector-chooseall:hover, a.active.selector-clearall:hover { + cursor: pointer; +} + +a.selector-chooseall { + padding: 0 18px 0 0; + background: url(../img/selector-icons.svg) right -160px no-repeat; + cursor: default; +} + +a.active.selector-chooseall:focus, a.active.selector-chooseall:hover { + background-position: 100% -176px; +} + +a.selector-clearall { + padding: 0 0 0 18px; + background: url(../img/selector-icons.svg) 0 -128px no-repeat; + cursor: default; +} + +a.active.selector-clearall:focus, a.active.selector-clearall:hover { + background-position: 0 -144px; +} + +/* STACKED SELECTORS */ + +.stacked { + float: left; + width: 490px; +} + +.stacked select { + width: 480px; + height: 10.1em; +} + +.stacked .selector-available, .stacked .selector-chosen { + width: 480px; +} + +.stacked .selector-available { + margin-bottom: 0; +} + +.stacked .selector-available input { + width: 422px; +} + +.stacked ul.selector-chooser { + height: 22px; + width: 50px; + margin: 0 0 10px 40%; + background-color: #eee; + border-radius: 10px; +} + +.stacked .selector-chooser li { + float: left; + padding: 3px 3px 3px 5px; +} + +.stacked .selector-chooseall, .stacked .selector-clearall { + display: none; +} + +.stacked .selector-add { + background: url(../img/selector-icons.svg) 0 -32px no-repeat; + cursor: default; +} + +.stacked .active.selector-add { + background-position: 0 -48px; + cursor: pointer; +} + +.stacked .selector-remove { + background: url(../img/selector-icons.svg) 0 0 no-repeat; + cursor: default; +} + +.stacked .active.selector-remove { + background-position: 0 -16px; + cursor: pointer; +} + +.selector .help-icon { + background: url(../img/icon-unknown.svg) 0 0 no-repeat; + display: inline-block; + vertical-align: middle; + margin: -2px 0 0 2px; + width: 13px; + height: 13px; +} + +.selector .selector-chosen .help-icon { + background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat; +} + +.selector .search-label-icon { + background: url(../img/search.svg) 0 0 no-repeat; + display: inline-block; + height: 18px; + width: 18px; +} + +/* DATE AND TIME */ + +p.datetime { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-weight: bold; +} + +.datetime span { + white-space: nowrap; + font-weight: normal; + font-size: 11px; + color: #ccc; +} + +.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField { + min-width: 0; + margin-left: 5px; + margin-bottom: 4px; +} + +table p.datetime { + font-size: 11px; + margin-left: 0; + padding-left: 0; +} + +.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon { + position: relative; + display: inline-block; + vertical-align: middle; + height: 16px; + width: 16px; + overflow: hidden; +} + +.datetimeshortcuts .clock-icon { + background: url(../img/icon-clock.svg) 0 0 no-repeat; +} + +.datetimeshortcuts a:focus .clock-icon, +.datetimeshortcuts a:hover .clock-icon { + background-position: 0 -16px; +} + +.datetimeshortcuts .date-icon { + background: url(../img/icon-calendar.svg) 0 0 no-repeat; + top: -1px; +} + +.datetimeshortcuts a:focus .date-icon, +.datetimeshortcuts a:hover .date-icon { + background-position: 0 -16px; +} + +.timezonewarning { + font-size: 11px; + color: #999; +} + +/* URL */ + +p.url { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.url a { + font-weight: normal; +} + +/* FILE UPLOADS */ + +p.file-upload { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.aligned p.file-upload { + margin-left: 170px; +} + +.file-upload a { + font-weight: normal; +} + +.file-upload .deletelink { + margin-left: 5px; +} + +span.clearable-file-input label { + color: #333; + font-size: 11px; + display: inline; + float: none; +} + +/* CALENDARS & CLOCKS */ + +.calendarbox, .clockbox { + margin: 5px auto; + font-size: 12px; + width: 19em; + text-align: center; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); + overflow: hidden; + position: relative; +} + +.clockbox { + width: auto; +} + +.calendar { + margin: 0; + padding: 0; +} + +.calendar table { + margin: 0; + padding: 0; + border-collapse: collapse; + background: white; + width: 100%; +} + +.calendar caption, .calendarbox h2 { + margin: 0; + text-align: center; + border-top: none; + background: #f5dd5d; + font-weight: 700; + font-size: 12px; + color: #333; +} + +.calendar th { + padding: 8px 5px; + background: #f8f8f8; + border-bottom: 1px solid #ddd; + font-weight: 400; + font-size: 12px; + text-align: center; + color: #666; +} + +.calendar td { + font-weight: 400; + font-size: 12px; + text-align: center; + padding: 0; + border-top: 1px solid #eee; + border-bottom: none; +} + +.calendar td.selected a { + background: #000; + color: #fff; +} + +.calendar td.nonday { + background: #f8f8f8; +} + +.calendar td.today a { + font-weight: 700; +} + +.calendar td a, .timelist a { + display: block; + font-weight: 400; + padding: 6px; + text-decoration: none; + color: #444; +} + +.calendar td a:focus, .timelist a:focus, +.calendar td a:hover, .timelist a:hover { + background: #000; + color: white; +} + +.calendar td a:active, .timelist a:active { + background: #417690; + color: white; +} + +.calendarnav { + font-size: 10px; + text-align: center; + color: #ccc; + margin: 0; + padding: 1px 3px; +} + +.calendarnav a:link, #calendarnav a:visited, +#calendarnav a:focus, #calendarnav a:hover { + color: #999; +} + +.calendar-shortcuts { + background: white; + font-size: 11px; + line-height: 11px; + border-top: 1px solid #eee; + padding: 8px 0; + color: #ccc; +} + +.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { + display: block; + position: absolute; + top: 8px; + width: 15px; + height: 15px; + text-indent: -9999px; + padding: 0; +} + +.calendarnav-previous { + left: 10px; + background: url(../img/calendar-icons.svg) 0 0 no-repeat; +} + +.calendarbox .calendarnav-previous:focus, +.calendarbox .calendarnav-previous:hover { + background-position: 0 -15px; +} + +.calendarnav-next { + right: 10px; + background: url(../img/calendar-icons.svg) 0 -30px no-repeat; +} + +.calendarbox .calendarnav-next:focus, +.calendarbox .calendarnav-next:hover { + background-position: 0 -45px; +} + +.calendar-cancel { + margin: 0; + padding: 4px 0; + font-size: 12px; + background: #eee; + border-top: 1px solid #ddd; + color: #333; +} + +.calendar-cancel:focus, .calendar-cancel:hover { + background: #ddd; +} + +.calendar-cancel a { + color: black; + display: block; +} + +ul.timelist, .timelist li { + list-style-type: none; + margin: 0; + padding: 0; +} + +.timelist a { + padding: 2px; +} + +/* EDIT INLINE */ + +.inline-deletelink { + float: right; + text-indent: -9999px; + background: url(../img/inline-delete.svg) 0 0 no-repeat; + width: 16px; + height: 16px; + border: 0px none; +} + +.inline-deletelink:focus, .inline-deletelink:hover { + cursor: pointer; +} + +/* RELATED WIDGET WRAPPER */ +.related-widget-wrapper { + float: left; /* display properly in form rows with multiple fields */ + overflow: hidden; /* clear floated contents */ +} + +.related-widget-wrapper-link { + opacity: 0.3; +} + +.related-widget-wrapper-link:link { + opacity: .8; +} + +.related-widget-wrapper-link:link:focus, +.related-widget-wrapper-link:link:hover { + opacity: 1; +} + +select + .related-widget-wrapper-link, +.related-widget-wrapper-link + .related-widget-wrapper-link { + margin-left: 7px; +} diff --git a/jdav_web/templates/admin/base.html b/jdav_web/templates/admin/base.html new file mode 100644 index 0000000..d1e7531 --- /dev/null +++ b/jdav_web/templates/admin/base.html @@ -0,0 +1,90 @@ +{% load i18n static %} +{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} + + +{% block title %}{% endblock %} + + +{% block extrastyle %}{% endblock %} +{% if LANGUAGE_BIDI %}{% endif %} +{% block extrahead %}{% endblock %} +{% block blockbots %}{% endblock %} + +{% load i18n %} + + + + +
+ + {% if not is_popup %} + + + + {% block breadcrumbs %} + + {% endblock %} + {% endif %} + + {% block messages %} + {% if messages %} +
    {% for message in messages %} + {{ message|capfirst }} + {% endfor %}
+ {% endif %} + {% endblock messages %} + + +
+ {% block pretitle %}{% endblock %} + {% block content_title %}{% if title %}

{{ title }}

{% endif %}{% endblock %} + {% block content %} + {% block object-tools %}{% endblock %} + {{ content }} + {% endblock %} + {% block sidebar %}{% endblock %} +
+
+ + + {% block footer %}{% endblock %} +
+ + + + diff --git a/jdav_web/templates/admin/base_site.html b/jdav_web/templates/admin/base_site.html new file mode 100644 index 0000000..495c2c1 --- /dev/null +++ b/jdav_web/templates/admin/base_site.html @@ -0,0 +1,12 @@ +{% extends "admin/base.html" %} +{% load i18n %} + +{% block title %} +{{ title }} | {% trans "JDAV LB Administration" %} +{% endblock %} + +{% block branding %} +

{% trans "JDAV LB Administration" %}

+{% endblock %} + +{% block nav-global %}{% endblock %}