Merge branch 'master' into test/views

This commit is contained in:
Martin Pépin 2017-10-10 15:39:18 +02:00
commit 3d22a1b029
17 changed files with 252 additions and 98 deletions

View file

@ -1,24 +1,24 @@
services: services:
- mysql:latest - postgres:latest
- redis:latest - redis:latest
variables: variables:
# GestioCOF settings # GestioCOF settings
DJANGO_SETTINGS_MODULE: "cof.settings_dev" DJANGO_SETTINGS_MODULE: "cof.settings.prod"
DBNAME: "cof_gestion" DBHOST: "postgres"
DBUSER: "cof_gestion"
DBPASSWD: "cof_password"
DBHOST: "mysql"
REDIS_HOST: "redis" REDIS_HOST: "redis"
REDIS_PASSWD: "dummy"
# Cached packages # Cached packages
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python" PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
# mysql service configuration # postgres service configuration
MYSQL_DATABASE: "$DBNAME" POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
MYSQL_USER: "$DBUSER" POSTGRES_USER: "cof_gestion"
MYSQL_PASSWORD: "$DBPASSWD" POSTGRES_DB: "cof_gestion"
MYSQL_ROOT_PASSWORD: "root_password"
# psql password authentication
PGPASSWORD: $POSTGRES_PASSWORD
cache: cache:
@ -29,15 +29,14 @@ cache:
before_script: before_script:
- mkdir -p vendor/{python,pip,apt} - mkdir -p vendor/{python,pip,apt}
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client - apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST" - sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
-e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'"
# Remove the old test database if it has not been done yet # Remove the old test database if it has not been done yet
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST" - psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
-e "DROP DATABASE test_$DBNAME" || true - pip install --cache-dir vendor/pip -t vendor/python -r requirements.txt
- pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt - redis-cli config set requirepass $REDIS_PASSWD || true
test: test:
stage: test stage: test
script: script:
- python manage.py test - python manage.py test -v3

View file

@ -56,17 +56,17 @@ class AttributionInline(admin.TabularInline):
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) qs = super().get_queryset(request)
if self.listing is not None: if self.listing is not None:
qs.filter(spectacle__listing=self.listing) qs = qs.filter(spectacle__listing=self.listing)
return qs return qs
class WithListingAttributionInline(AttributionInline): class WithListingAttributionInline(AttributionInline):
exclude = ('given', )
form = WithListingAttributionTabularAdminForm form = WithListingAttributionTabularAdminForm
listing = True listing = True
class WithoutListingAttributionInline(AttributionInline): class WithoutListingAttributionInline(AttributionInline):
exclude = ('given', )
form = WithoutListingAttributionTabularAdminForm form = WithoutListingAttributionTabularAdminForm
listing = False listing = False

View file

@ -21,6 +21,7 @@ ALLOWED_HOSTS = [
STATIC_ROOT = os.path.join( STATIC_ROOT = os.path.join(
os.path.dirname(os.path.dirname(BASE_DIR)), os.path.dirname(os.path.dirname(BASE_DIR)),
"public", "public",
"gestion",
"static", "static",
) )

View file

@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.forms.formsets import BaseFormSet, formset_factory from django.forms.formsets import BaseFormSet, formset_factory
from django.core.validators import MinLengthValidator
from djconfig.forms import ConfigForm from djconfig.forms import ConfigForm
@ -197,9 +196,6 @@ class RegistrationUserForm(forms.ModelForm):
super(RegistrationUserForm, self).__init__(*args, **kw) super(RegistrationUserForm, self).__init__(*args, **kw)
self.fields['username'].help_text = "" self.fields['username'].help_text = ""
def force_long_username(self):
self.fields['username'].validators = [MinLengthValidator(9)]
class Meta: class Meta:
model = User model = User
fields = ("username", "first_name", "last_name", "email") fields = ("username", "first_name", "last_name", "email")

View file

@ -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),
),
]

View file

@ -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 = [
]

View file

@ -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')
]),
),
]

View file

@ -8,23 +8,6 @@ from gestioncof.petits_cours_models import choices_length
from bda.models import Spectacle 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 = ( TYPE_COMMENT_FIELD = (
('text', _("Texte long")), ('text', _("Texte long")),
('char', _("Texte court")), ('char', _("Texte court")),
@ -32,8 +15,44 @@ TYPE_COMMENT_FIELD = (
class CofProfile(models.Model): 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") 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) is_cof = models.BooleanField("Membre du COF", default=False)
phone = models.CharField("Téléphone", max_length=20, blank=True) phone = models.CharField("Téléphone", max_length=20, blank=True)
occupation = models.CharField(_("Occupation"), occupation = models.CharField(_("Occupation"),

View file

@ -10,7 +10,7 @@ export_patterns = [
url(r'^mega/avecremarques$', views.export_mega_remarksonly), url(r'^mega/avecremarques$', views.export_mega_remarksonly),
url(r'^mega/participants$', views.export_mega_participants), url(r'^mega/participants$', views.export_mega_participants),
url(r'^mega/orgas$', views.export_mega_orgas), url(r'^mega/orgas$', views.export_mega_orgas),
url(r'^mega/(?P<type>.+)$', views.export_mega_bytype), # url(r'^mega/(?P<type>.+)$', views.export_mega_bytype),
url(r'^mega$', views.export_mega), url(r'^mega$', views.export_mega),
] ]

View file

@ -398,7 +398,6 @@ def registration_form2(request, login_clipper=None, username=None,
elif not login_clipper: elif not login_clipper:
# new user # new user
user_form = RegistrationPassUserForm() user_form = RegistrationPassUserForm()
user_form.force_long_username()
profile_form = RegistrationProfileForm() profile_form = RegistrationProfileForm()
event_formset = EventFormset(events=events, prefix='events') event_formset = EventFormset(events=events, prefix='events')
clubs_form = ClubsForm() clubs_form = ClubsForm()
@ -437,12 +436,10 @@ def registration(request):
user_form = RegistrationUserForm(request_dict, instance=member) user_form = RegistrationUserForm(request_dict, instance=member)
if member.profile.login_clipper: if member.profile.login_clipper:
login_clipper = member.profile.login_clipper login_clipper = member.profile.login_clipper
else:
user_form.force_long_username()
except User.DoesNotExist: except User.DoesNotExist:
user_form.force_long_username() pass
else: else:
user_form.force_long_username() pass
# ----- # -----
# Validation des formulaires # Validation des formulaires
@ -609,13 +606,13 @@ def csv_export_mega(filename, qs):
@buro_required @buro_required
def export_mega_remarksonly(request): def export_mega_remarksonly(request):
filename = 'remarques_mega_2016.csv' filename = 'remarques_mega_2017.csv'
response = HttpResponse(content_type='text/csv') response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename response['Content-Disposition'] = 'attachment; filename=' + filename
writer = unicodecsv.writer(response) writer = unicodecsv.writer(response)
event = Event.objects.get(title="Mega 2016") event = Event.objects.get(title="MEGA 2017")
commentfield = event.commentfields.get(name="Commentaires") commentfield = event.commentfields.get(name="Commentaire")
for val in commentfield.values.all(): for val in commentfield.values.all():
reg = val.registration reg = val.registration
user = reg.user user = reg.user
@ -627,50 +624,52 @@ def export_mega_remarksonly(request):
return response return response
@buro_required # @buro_required
def export_mega_bytype(request, type): # def export_mega_bytype(request, type):
types = {"orga-actif": "Orga élève", # types = {"orga-actif": "Orga élève",
"orga-branleur": "Orga étudiant", # "orga-branleur": "Orga étudiant",
"conscrit-eleve": "Conscrit élève", # "conscrit-eleve": "Conscrit élève",
"conscrit-etudiant": "Conscrit étudiant"} # "conscrit-etudiant": "Conscrit étudiant"}
#
if type not in types: # if type not in types:
raise Http404 # raise Http404
#
event = Event.objects.get(title="Mega 2016") # event = Event.objects.get(title="MEGA 2017")
type_option = event.options.get(name="Type") # type_option = event.options.get(name="Type")
participant_type = type_option.choices.get(value=types[type]).id # participant_type = type_option.choices.get(value=types[type]).id
qs = EventRegistration.objects.filter(event=event).filter( # qs = EventRegistration.objects.filter(event=event).filter(
options__id__exact=participant_type) # options__id__exact=participant_type)
return csv_export_mega(type + '_mega_2016.csv', qs) # return csv_export_mega(type + '_mega_2017.csv', qs)
@buro_required @buro_required
def export_mega_orgas(request): def export_mega_orgas(request):
event = Event.objects.get(title="Mega 2016") event = Event.objects.get(title="MEGA 2017")
type_option = event.options.get(name="Conscrit ou orga ?") type_option = event.options.get(name="Conscrit/Orga ?")
participant_type = type_option.choices.get(value="Vieux").id participant_type = type_option.choices.get(value="Orga").id
qs = EventRegistration.objects.filter(event=event).exclude( qs = EventRegistration.objects.filter(event=event).filter(
options__id=participant_type) options__id=participant_type
return csv_export_mega('orgas_mega_2016.csv', qs) )
return csv_export_mega('orgas_mega_2017.csv', qs)
@buro_required @buro_required
def export_mega_participants(request): def export_mega_participants(request):
event = Event.objects.get(title="Mega 2016") event = Event.objects.get(title="MEGA 2017")
type_option = event.options.get(name="Conscrit ou orga ?") type_option = event.options.get(name="Conscrit/Orga ?")
participant_type = type_option.choices.get(value="Conscrit").id participant_type = type_option.choices.get(value="Conscrit").id
qs = EventRegistration.objects.filter(event=event).filter( qs = EventRegistration.objects.filter(event=event).filter(
options__id=participant_type) options__id=participant_type
return csv_export_mega('participants_mega_2016.csv', qs) )
return csv_export_mega('participants_mega_2017.csv', qs)
@buro_required @buro_required
def export_mega(request): def export_mega(request):
event = Event.objects.filter(title="Mega 2016") event = Event.objects.filter(title="MEGA 2017")
qs = EventRegistration.objects.filter(event=event) \ qs = EventRegistration.objects.filter(event=event) \
.order_by("user__username") .order_by("user__username")
return csv_export_mega('all_mega_2016.csv', qs) return csv_export_mega('all_mega_2017.csv', qs)
@buro_required @buro_required

View file

@ -5,7 +5,6 @@ from decimal import Decimal
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms import modelformset_factory, widgets from django.forms import modelformset_factory, widgets
@ -110,21 +109,16 @@ class CofRestrictForm(CofForm):
class Meta(CofForm.Meta): class Meta(CofForm.Meta):
fields = ['departement'] 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: class Meta:
model = User model = User
fields = ['username', 'first_name', 'last_name', 'email'] fields = ['username', 'first_name', 'last_name', 'email']
help_texts = { help_texts = {
'username': '' 'username': ''
} }
class UserRestrictForm(UserForm): class UserRestrictForm(UserForm):
class Meta(UserForm.Meta): class Meta(UserForm.Meta):
fields = ['first_name', 'last_name'] fields = ['first_name', 'last_name']
@ -173,10 +167,22 @@ class GroupForm(forms.ModelForm):
name = self.cleaned_data['name'] name = self.cleaned_data['name']
return 'K-Fêt %s' % 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: class Meta:
model = Group model = Group
fields = ['name', 'permissions'] fields = ['name', 'permissions']
class AccountNegativeForm(forms.ModelForm): class AccountNegativeForm(forms.ModelForm):
class Meta: class Meta:
model = AccountNegative model = AccountNegative

View file

@ -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),
),
]

View file

@ -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 = [
]

View file

@ -14,10 +14,16 @@
.kfetopen .base { .kfetopen .base {
height: 50px; 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; align-items: center;
justify-content: center;
} }
.kfetopen .details { .kfetopen .details {
@ -34,10 +40,23 @@
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
transition: background 0.15s; transition: background 0.15s;
margin: 3px;
} }
.kfetopen .warning { .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 { .kfetopen .status-text {

View file

@ -74,10 +74,10 @@ OpenKfet.prototype = {
if (this.admin) { if (this.admin) {
this.add_class(this.admin_status); this.add_class(this.admin_status);
if (this.force_close) { 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']); this.dom.force_close_btn.html(this.force_text['deactivate']);
} else { } else {
this.dom.warning.removeClass('in'); this.dom.warning.removeClass('in').hide();
this.dom.force_close_btn.html(this.force_text['activate']); this.dom.force_close_btn.html(this.force_text['activate']);
} }
} }

View file

@ -94,7 +94,7 @@
{% endif %} {% endif %}
<li class="divider"></li> <li class="divider"></li>
<li> <li>
<a href="{% url "cof-logout" %}?next=/k-fet/"> <a href="{% url "cof-logout" %}?next={% slugurl "k-fet" %}">
<span class="glyphicon glyphicon-log-out"></span><span>Déconnexion</span> <span class="glyphicon glyphicon-log-out"></span><span>Déconnexion</span>
</a> </a>
</li> </li>
@ -103,7 +103,7 @@
{% endif %} {% endif %}
{% if user.is_authenticated and not perms.kfet.is_team %} {% if user.is_authenticated and not perms.kfet.is_team %}
<li> <li>
<a href="{% url "cof-logout" %}?next=/k-fet/" title="Déconnexion"> <a href="{% url "cof-logout" %}?next={% slugurl "k-fet" %}" title="Déconnexion">
<span class="glyphicon glyphicon-log-out"></span> <span class="glyphicon glyphicon-log-out"></span>
</a> </a>
</li> </li>

View file

@ -268,10 +268,10 @@ def get_account_create_forms(request=None, username=None, login_clipper=None,
# Form créations # Form créations
if request: 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) cof_form = CofForm(request.POST, initial=cof_initial)
else: else:
user_form = UserForm(initial=user_initial, from_clipper=True) user_form = UserForm(initial=user_initial)
cof_form = CofForm(initial=cof_initial) cof_form = CofForm(initial=cof_initial)
# Protection (read-only) des champs username et login_clipper # Protection (read-only) des champs username et login_clipper