diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2635b7b..85be668b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,24 +1,24 @@ services: - - mysql:latest + - postgres:latest - redis:latest variables: # GestioCOF settings - DJANGO_SETTINGS_MODULE: "cof.settings_dev" - DBNAME: "cof_gestion" - DBUSER: "cof_gestion" - DBPASSWD: "cof_password" - DBHOST: "mysql" + DJANGO_SETTINGS_MODULE: "cof.settings.prod" + DBHOST: "postgres" REDIS_HOST: "redis" + REDIS_PASSWD: "dummy" # Cached packages PYTHONPATH: "$CI_PROJECT_DIR/vendor/python" - # mysql service configuration - MYSQL_DATABASE: "$DBNAME" - MYSQL_USER: "$DBUSER" - MYSQL_PASSWORD: "$DBPASSWD" - MYSQL_ROOT_PASSWORD: "root_password" + # postgres service configuration + POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" + POSTGRES_USER: "cof_gestion" + POSTGRES_DB: "cof_gestion" + + # psql password authentication + PGPASSWORD: $POSTGRES_PASSWORD cache: @@ -29,15 +29,14 @@ cache: before_script: - mkdir -p vendor/{python,pip,apt} - - apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST" - -e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'" + - apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client + - sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py # Remove the old test database if it has not been done yet - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST" - -e "DROP DATABASE test_$DBNAME" || true - - pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt + - psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB" + - pip install --cache-dir vendor/pip -t vendor/python -r requirements.txt + - redis-cli config set requirepass $REDIS_PASSWD || true test: stage: test script: - - python manage.py test + - python manage.py test -v3 diff --git a/bda/admin.py b/bda/admin.py index 0cc66d43..60d3c1ba 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -56,17 +56,17 @@ class AttributionInline(admin.TabularInline): def get_queryset(self, request): qs = super().get_queryset(request) if self.listing is not None: - qs.filter(spectacle__listing=self.listing) + qs = qs.filter(spectacle__listing=self.listing) return qs class WithListingAttributionInline(AttributionInline): + exclude = ('given', ) form = WithListingAttributionTabularAdminForm listing = True class WithoutListingAttributionInline(AttributionInline): - exclude = ('given', ) form = WithoutListingAttributionTabularAdminForm listing = False diff --git a/cof/settings/prod.py b/cof/settings/prod.py index 286b5547..2ffdf02f 100644 --- a/cof/settings/prod.py +++ b/cof/settings/prod.py @@ -21,6 +21,7 @@ ALLOWED_HOSTS = [ STATIC_ROOT = os.path.join( os.path.dirname(os.path.dirname(BASE_DIR)), "public", + "gestion", "static", ) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 793f14e5..2124b7c8 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.forms.widgets import RadioSelect, CheckboxSelectMultiple from django.forms.formsets import BaseFormSet, formset_factory -from django.core.validators import MinLengthValidator from djconfig.forms import ConfigForm @@ -197,9 +196,6 @@ class RegistrationUserForm(forms.ModelForm): super(RegistrationUserForm, self).__init__(*args, **kw) self.fields['username'].help_text = "" - def force_long_username(self): - self.fields['username'].validators = [MinLengthValidator(9)] - class Meta: model = User fields = ("username", "first_name", "last_name", "email") diff --git a/gestioncof/migrations/0011_longer_clippers.py b/gestioncof/migrations/0011_longer_clippers.py new file mode 100644 index 00000000..631d0ea8 --- /dev/null +++ b/gestioncof/migrations/0011_longer_clippers.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0010_delete_custommail'), + ] + + operations = [ + migrations.AlterField( + model_name='cofprofile', + name='login_clipper', + field=models.CharField(verbose_name='Login clipper', blank=True, max_length=32), + ), + ] diff --git a/gestioncof/migrations/0012_merge.py b/gestioncof/migrations/0012_merge.py new file mode 100644 index 00000000..39879346 --- /dev/null +++ b/gestioncof/migrations/0012_merge.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0011_remove_cofprofile_num'), + ('gestioncof', '0011_longer_clippers'), + ] + + operations = [ + ] diff --git a/gestioncof/migrations/0013_pei.py b/gestioncof/migrations/0013_pei.py new file mode 100644 index 00000000..2fbddf1f --- /dev/null +++ b/gestioncof/migrations/0013_pei.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0012_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='cofprofile', + name='occupation', + field=models.CharField( + verbose_name='Occupation', + max_length=9, + default='1A', + choices=[ + ('exterieur', 'Extérieur'), + ('1A', '1A'), + ('2A', '2A'), + ('3A', '3A'), + ('4A', '4A'), + ('archicube', 'Archicube'), + ('doctorant', 'Doctorant'), + ('CST', 'CST'), + ('PEI', 'PEI') + ]), + ), + migrations.AlterField( + model_name='cofprofile', + name='type_cotiz', + field=models.CharField( + verbose_name='Type de cotisation', + max_length=9, + default='normalien', + choices=[ + ('etudiant', 'Normalien étudiant'), + ('normalien', 'Normalien élève'), + ('exterieur', 'Extérieur'), + ('gratis', 'Gratuit') + ]), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index f1ad35e1..ea2cacc4 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -8,23 +8,6 @@ from gestioncof.petits_cours_models import choices_length from bda.models import Spectacle -OCCUPATION_CHOICES = ( - ('exterieur', _("Extérieur")), - ('1A', _("1A")), - ('2A', _("2A")), - ('3A', _("3A")), - ('4A', _("4A")), - ('archicube', _("Archicube")), - ('doctorant', _("Doctorant")), - ('CST', _("CST")), -) - -TYPE_COTIZ_CHOICES = ( - ('etudiant', _("Normalien étudiant")), - ('normalien', _("Normalien élève")), - ('exterieur', _("Extérieur")), -) - TYPE_COMMENT_FIELD = ( ('text', _("Texte long")), ('char', _("Texte court")), @@ -32,8 +15,44 @@ TYPE_COMMENT_FIELD = ( class CofProfile(models.Model): + STATUS_EXTE = "exterieur" + STATUS_1A = "1A" + STATUS_2A = "2A" + STATUS_3A = "3A" + STATUS_4A = "4A" + STATUS_ARCHI = "archicube" + STATUS_DOCTORANT = "doctorant" + STATUS_CST = "CST" + STATUS_PEI = "PEI" + + OCCUPATION_CHOICES = ( + (STATUS_EXTE, _("Extérieur")), + (STATUS_1A, _("1A")), + (STATUS_2A, _("2A")), + (STATUS_3A, _("3A")), + (STATUS_4A, _("4A")), + (STATUS_ARCHI, _("Archicube")), + (STATUS_DOCTORANT, _("Doctorant")), + (STATUS_CST, _("CST")), + (STATUS_PEI, _("PEI")), + ) + + COTIZ_ETUDIANT = "etudiant" + COTIZ_NORMALIEN = "normalien" + COTIZ_EXTE = "exterieur" + COTIZ_GRATIS = "gratis" + + TYPE_COTIZ_CHOICES = ( + (COTIZ_ETUDIANT, _("Normalien étudiant")), + (COTIZ_NORMALIEN, _("Normalien élève")), + (COTIZ_EXTE, _("Extérieur")), + (COTIZ_GRATIS, _("Gratuit")), + ) + user = models.OneToOneField(User, related_name="profile") - login_clipper = models.CharField("Login clipper", max_length=8, blank=True) + login_clipper = models.CharField( + "Login clipper", max_length=32, blank=True + ) is_cof = models.BooleanField("Membre du COF", default=False) phone = models.CharField("Téléphone", max_length=20, blank=True) occupation = models.CharField(_("Occupation"), diff --git a/gestioncof/urls.py b/gestioncof/urls.py index 9a562e7e..57c2e8f2 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -10,7 +10,7 @@ export_patterns = [ url(r'^mega/avecremarques$', views.export_mega_remarksonly), url(r'^mega/participants$', views.export_mega_participants), url(r'^mega/orgas$', views.export_mega_orgas), - url(r'^mega/(?P.+)$', views.export_mega_bytype), + # url(r'^mega/(?P.+)$', views.export_mega_bytype), url(r'^mega$', views.export_mega), ] diff --git a/gestioncof/views.py b/gestioncof/views.py index fffd5751..c5701510 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -398,7 +398,6 @@ def registration_form2(request, login_clipper=None, username=None, elif not login_clipper: # new user user_form = RegistrationPassUserForm() - user_form.force_long_username() profile_form = RegistrationProfileForm() event_formset = EventFormset(events=events, prefix='events') clubs_form = ClubsForm() @@ -437,12 +436,10 @@ def registration(request): user_form = RegistrationUserForm(request_dict, instance=member) if member.profile.login_clipper: login_clipper = member.profile.login_clipper - else: - user_form.force_long_username() except User.DoesNotExist: - user_form.force_long_username() + pass else: - user_form.force_long_username() + pass # ----- # Validation des formulaires @@ -609,13 +606,13 @@ def csv_export_mega(filename, qs): @buro_required def export_mega_remarksonly(request): - filename = 'remarques_mega_2016.csv' + filename = 'remarques_mega_2017.csv' response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename=' + filename writer = unicodecsv.writer(response) - event = Event.objects.get(title="Mega 2016") - commentfield = event.commentfields.get(name="Commentaires") + event = Event.objects.get(title="MEGA 2017") + commentfield = event.commentfields.get(name="Commentaire") for val in commentfield.values.all(): reg = val.registration user = reg.user @@ -627,50 +624,52 @@ def export_mega_remarksonly(request): return response -@buro_required -def export_mega_bytype(request, type): - types = {"orga-actif": "Orga élève", - "orga-branleur": "Orga étudiant", - "conscrit-eleve": "Conscrit élève", - "conscrit-etudiant": "Conscrit étudiant"} - - if type not in types: - raise Http404 - - event = Event.objects.get(title="Mega 2016") - type_option = event.options.get(name="Type") - participant_type = type_option.choices.get(value=types[type]).id - qs = EventRegistration.objects.filter(event=event).filter( - options__id__exact=participant_type) - return csv_export_mega(type + '_mega_2016.csv', qs) +# @buro_required +# def export_mega_bytype(request, type): +# types = {"orga-actif": "Orga élève", +# "orga-branleur": "Orga étudiant", +# "conscrit-eleve": "Conscrit élève", +# "conscrit-etudiant": "Conscrit étudiant"} +# +# if type not in types: +# raise Http404 +# +# event = Event.objects.get(title="MEGA 2017") +# type_option = event.options.get(name="Type") +# participant_type = type_option.choices.get(value=types[type]).id +# qs = EventRegistration.objects.filter(event=event).filter( +# options__id__exact=participant_type) +# return csv_export_mega(type + '_mega_2017.csv', qs) @buro_required def export_mega_orgas(request): - event = Event.objects.get(title="Mega 2016") - type_option = event.options.get(name="Conscrit ou orga ?") - participant_type = type_option.choices.get(value="Vieux").id - qs = EventRegistration.objects.filter(event=event).exclude( - options__id=participant_type) - return csv_export_mega('orgas_mega_2016.csv', qs) + event = Event.objects.get(title="MEGA 2017") + type_option = event.options.get(name="Conscrit/Orga ?") + participant_type = type_option.choices.get(value="Orga").id + qs = EventRegistration.objects.filter(event=event).filter( + options__id=participant_type + ) + return csv_export_mega('orgas_mega_2017.csv', qs) @buro_required def export_mega_participants(request): - event = Event.objects.get(title="Mega 2016") - type_option = event.options.get(name="Conscrit ou orga ?") + event = Event.objects.get(title="MEGA 2017") + type_option = event.options.get(name="Conscrit/Orga ?") participant_type = type_option.choices.get(value="Conscrit").id qs = EventRegistration.objects.filter(event=event).filter( - options__id=participant_type) - return csv_export_mega('participants_mega_2016.csv', qs) + options__id=participant_type + ) + return csv_export_mega('participants_mega_2017.csv', qs) @buro_required def export_mega(request): - event = Event.objects.filter(title="Mega 2016") + event = Event.objects.filter(title="MEGA 2017") qs = EventRegistration.objects.filter(event=event) \ .order_by("user__username") - return csv_export_mega('all_mega_2016.csv', qs) + return csv_export_mega('all_mega_2017.csv', qs) @buro_required diff --git a/kfet/forms.py b/kfet/forms.py index 5190689d..6ef3aefb 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -5,7 +5,6 @@ from decimal import Decimal from django import forms from django.core.exceptions import ValidationError -from django.core.validators import MinLengthValidator from django.contrib.auth.models import User, Group, Permission from django.contrib.contenttypes.models import ContentType from django.forms import modelformset_factory, widgets @@ -110,21 +109,16 @@ class CofRestrictForm(CofForm): class Meta(CofForm.Meta): fields = ['departement'] -class UserForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - from_clipper = kwargs.pop('from_clipper', False) - new_user = kwargs.get('instance') is None and not from_clipper - super(UserForm, self).__init__(*args, **kwargs) - if new_user: - self.fields['username'].validators = [MinLengthValidator(9)] +class UserForm(forms.ModelForm): class Meta: - model = User + model = User fields = ['username', 'first_name', 'last_name', 'email'] help_texts = { 'username': '' } + class UserRestrictForm(UserForm): class Meta(UserForm.Meta): fields = ['first_name', 'last_name'] @@ -173,10 +167,22 @@ class GroupForm(forms.ModelForm): name = self.cleaned_data['name'] return 'K-Fêt %s' % name + def clean_permissions(self): + kfet_perms = self.cleaned_data['permissions'] + # TODO: With Django >=1.11, the QuerySet method 'difference' can be used. + # other_groups = self.instance.permissions.difference( + # self.fields['permissions'].queryset + # ) + other_perms = self.instance.permissions.exclude( + pk__in=[p.pk for p in self.fields['permissions'].queryset], + ) + return list(kfet_perms) + list(other_perms) + class Meta: - model = Group + model = Group fields = ['name', 'permissions'] + class AccountNegativeForm(forms.ModelForm): class Meta: model = AccountNegative diff --git a/kfet/migrations/0054_update_promos.py b/kfet/migrations/0054_update_promos.py new file mode 100644 index 00000000..2691e903 --- /dev/null +++ b/kfet/migrations/0054_update_promos.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0053_created_at'), + ] + + operations = [ + migrations.AlterField( + model_name='account', + name='promo', + field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017)], default=2017, null=True), + ), + ] diff --git a/kfet/migrations/0057_merge.py b/kfet/migrations/0057_merge.py new file mode 100644 index 00000000..48f63399 --- /dev/null +++ b/kfet/migrations/0057_merge.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0056_change_account_meta'), + ('kfet', '0054_update_promos'), + ] + + operations = [ + ] diff --git a/kfet/open/static/kfetopen/kfet-open.css b/kfet/open/static/kfetopen/kfet-open.css index d44318cd..a7068626 100644 --- a/kfet/open/static/kfetopen/kfet-open.css +++ b/kfet/open/static/kfetopen/kfet-open.css @@ -14,10 +14,16 @@ .kfetopen .base { height: 50px; - padding: 15px; + max-width: 16px; - display: inline-flex; + margin-left: 5px; + margin-right: 5px; + + display: flex; + flex-wrap: wrap; + align-content: center; align-items: center; + justify-content: center; } .kfetopen .details { @@ -34,10 +40,23 @@ height: 10px; border-radius: 50%; transition: background 0.15s; + margin: 3px; } .kfetopen .warning { - margin-left: 15px; + display: none; +} + +@media (min-width: 576px) { + .kfetopen .base { + max-width: none; + margin-left: 15px; + margin-right: 15px; + } + + .kfetopen .warning { + margin-left: 15px; + } } .kfetopen .status-text { diff --git a/kfet/open/static/kfetopen/kfet-open.js b/kfet/open/static/kfetopen/kfet-open.js index b86cc5bc..74f18d8a 100644 --- a/kfet/open/static/kfetopen/kfet-open.js +++ b/kfet/open/static/kfetopen/kfet-open.js @@ -74,10 +74,10 @@ OpenKfet.prototype = { if (this.admin) { this.add_class(this.admin_status); if (this.force_close) { - this.dom.warning.addClass('in'); + this.dom.warning.show().addClass('in'); this.dom.force_close_btn.html(this.force_text['deactivate']); } else { - this.dom.warning.removeClass('in'); + this.dom.warning.removeClass('in').hide(); this.dom.force_close_btn.html(this.force_text['activate']); } } diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index ba582bcf..abcb8e18 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -94,7 +94,7 @@ {% endif %}
  • - + Déconnexion
  • @@ -103,7 +103,7 @@ {% endif %} {% if user.is_authenticated and not perms.kfet.is_team %}
  • - +
  • diff --git a/kfet/views.py b/kfet/views.py index 5e451c9c..c7eb677b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -268,10 +268,10 @@ def get_account_create_forms(request=None, username=None, login_clipper=None, # Form créations if request: - user_form = UserForm(request.POST, initial=user_initial, from_clipper=True) + user_form = UserForm(request.POST, initial=user_initial) cof_form = CofForm(request.POST, initial=cof_initial) else: - user_form = UserForm(initial=user_initial, from_clipper=True) + user_form = UserForm(initial=user_initial) cof_form = CofForm(initial=cof_initial) # Protection (read-only) des champs username et login_clipper