Compare commits

..

1 commit

Author SHA1 Message Date
Aurélien Delobelle
82d361e775 API supports Activity
- Add ActivitySerializer + tests.
- Add 'places' field to ActivityTemplateSerializer.
2017-09-04 16:24:34 +02:00
189 changed files with 9189 additions and 9755 deletions

3
.gitignore vendored
View file

@ -1,8 +1,7 @@
.vagrant/ .vagrant/
__pycache__ __pycache__
venv venv
poulpe/settings.py evenementiel/settings.py
.*.swp .*.swp
*.pyc *.pyc
*.sqlite3 *.sqlite3
*.scssc

View file

@ -1 +0,0 @@
default_app_config = 'api.apps.APIConfig'

View file

@ -1,7 +0,0 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class APIConfig(AppConfig):
name = 'api'
verbose_name = _("API")

View file

@ -1,9 +1,6 @@
from django.db import transaction
from django.utils.decorators import method_decorator
from rest_framework import serializers from rest_framework import serializers
from event.models import Event, ActivityTag, Place, ActivityTemplate from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
from .fields import EventHyperlinkedIdentityField from .fields import EventHyperlinkedIdentityField
@ -40,19 +37,11 @@ class ActivityTagSerializer(serializers.ModelSerializer):
fields = ('url', 'id', 'name', 'is_public', 'color', 'event') fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
# TODO rajouter des permissions class BaseActivitySerializer(serializers.ModelSerializer):
class ActivityTemplateSerializer(serializers.ModelSerializer):
tags = ActivityTagSerializer(many=True) tags = ActivityTagSerializer(many=True)
serializer_url_field = EventHyperlinkedIdentityField serializer_url_field = EventHyperlinkedIdentityField
class Meta:
model = ActivityTemplate
fields = (
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
'max_perm', 'description', 'remarks', 'tags',
)
def process_tags(self, instance, tags_data): def process_tags(self, instance, tags_data):
# TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag # TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
# n'existe pas encore il faudra faire un premier passage sur `tags` i # n'existe pas encore il faudra faire un premier passage sur `tags` i
@ -88,5 +77,29 @@ class ActivityTemplateSerializer(serializers.ModelSerializer):
return activity_template return activity_template
class ActivitySerializer(serializers.ModelSerializer): # TODO rajouter des permissions
pass class ActivityTemplateSerializer(
BaseActivitySerializer,
serializers.ModelSerializer,
):
class Meta:
model = ActivityTemplate
fields = (
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
'max_perm', 'description', 'remarks', 'tags', 'places',
)
class ActivitySerializer(
BaseActivitySerializer,
serializers.ModelSerializer,
):
class Meta:
model = Activity
fields = (
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
'max_perm', 'description', 'remarks', 'tags', 'places',
'parent', 'staff', 'beginning', 'end',
)

View file

@ -5,7 +5,7 @@ from django.utils import timezone
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from event.models import Event, Place, ActivityTag, ActivityTemplate from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
from api.test.testcases import ModelAPITestCaseMixin from api.test.testcases import ModelAPITestCaseMixin
from api.test.utils import json_format from api.test.utils import json_format
@ -134,6 +134,11 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
'color': "#222", 'color': "#222",
}) })
self.place = Place.objects.create(
name='The Place to Be',
description='Super great',
)
def get_url_model(self, *args, **kwargs): def get_url_model(self, *args, **kwargs):
kwargs['event_pk'] = 1 kwargs['event_pk'] = 1
return super().get_url_model(*args, **kwargs) return super().get_url_model(*args, **kwargs)
@ -180,6 +185,7 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
'min_perm': instance.min_perm, 'min_perm': instance.min_perm,
'max_perm': instance.max_perm, 'max_perm': instance.max_perm,
'description': instance.description, 'description': instance.description,
'places': [place.pk for place in instance.places.all()],
}) })
@property @property
@ -196,7 +202,8 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
'is_public': False, 'is_public': False,
'remarks': "test remark", 'remarks': "test remark",
'event': self.event, 'event': self.event,
'tags': [self.activity_tag] 'tags': [self.activity_tag],
'places': [self.place],
}, },
] ]
@ -240,6 +247,7 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
'color': '#555', 'color': '#555',
}, },
], ],
'places': [str(self.place.pk)],
} }
@property @property
@ -254,7 +262,192 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
'title': "act temp3", 'title': "act temp3",
'is_public': False, 'is_public': False,
'remarks': "another test remark", 'remarks': "another test remark",
'tags': [tag_root, tag_bound] 'tags': [tag_root, tag_bound],
'places': [self.place],
}
class ActivityAPITests(ModelAPITestCaseMixin, APITestCase):
model = Activity
url_label = 'activity'
list_ordering = 'title'
def setUp(self):
super().setUp()
self.event = Event.objects.create(
title='Event',
slug='the-slug',
beginning_date=self.now + timedelta(days=10),
ending_date=self.now + timedelta(days=20),
description="La Nuit n'aura pas lieu.",
created_by=self.user,
)
self.activity_tag = ActivityTag.objects.create(
name='tag2',
is_public=False,
color='#222',
)
self.place = Place.objects.create(
name='The Place to Be',
description='Super great',
)
self.activity_template = ActivityTemplate.objects.create(
title=None,
event=self.event,
is_public=None,
has_perm=True,
min_perm=2,
max_perm=4,
description="Il faut tenir le quai.",
remarks="",
)
self.activity_template.tags.set([self.activity_tag])
self.activity_template.places.set([self.place])
def get_url_model(self, *args, **kwargs):
kwargs['event_pk'] = self.event.pk
return super().get_url_model(*args, **kwargs)
def get_url_object(self, *args, **kwargs):
kwargs['event_pk'] = self.event.pk
return super().get_url_object(*args, **kwargs)
def get_expected_data(self, instance):
return json_format({
'url': (
'http://testserver/api/event/{event_pk}/activity/{pk}/'
.format(event_pk=instance.event.pk, pk=instance.pk)
),
'id': instance.id,
'title': instance.title,
'is_public': instance.is_public,
'remarks': instance.remarks,
'event': instance.event.pk,
'tags': [
{
**(tag.event and {
'url': (
'http://testserver/api/event/{event_pk}/'
'tag/{tag_pk}/'
.format(event_pk=tag.event.pk, tag_pk=tag.pk)
),
'event': tag.event.pk,
} or {
'url': (
'http://testserver/api/tag/{tag_pk}/'
.format(tag_pk=tag.pk)
),
'event': None,
}),
'id': tag.id,
'name': tag.name,
'is_public': tag.is_public,
'color': tag.color,
}
for tag in instance.tags.all()
],
'has_perm': instance.has_perm,
'min_perm': instance.min_perm,
'max_perm': instance.max_perm,
'description': instance.description,
'places': [place.pk for place in instance.places.all()],
'parent': instance.parent.pk if instance.parent else None,
'staff': [u.pk for u in instance.staff.all()],
'beginning': instance.beginning,
'end': instance.end,
})
@property
def instances_data(self):
return [
{
'title': "act",
'is_public': True,
'remarks': "test remark",
'event': self.event,
'beginning': self.now + timedelta(days=12),
'end': self.now + timedelta(days=13),
},
{
'title': "act",
'is_public': False,
'remarks': "test remark",
'event': self.event,
'tags': [self.activity_tag],
'places': [self.place],
'beginning': self.now + timedelta(days=15),
'end': self.now + timedelta(days=18),
},
]
@property
def create_data(self):
return {
'title': "act",
'is_public': False,
'remarks': "test remark",
'tags': [
{
'name': "tag2",
'is_public': False,
'color': '#222',
},
],
'beginning': '2017-10-20 15:45:00',
'end': '2017-10-21 04:30:00',
}
@property
def create_expected(self):
return {
**self.create_data,
'tags': [ActivityTag.objects.get(name='tag2')],
'beginning': timezone.make_aware(
datetime(2017, 10, 20, 15, 45, 00)),
'end': timezone.make_aware(
datetime(2017, 10, 21, 4, 30, 00)),
}
@property
def update_data(self):
return {
'title': "act",
'is_public': False,
'remarks': "another test remark",
'tags': [
{
'name': "tag2",
'is_public': False,
'color': '#222',
},
{
'name': "body",
'is_public': True,
'color': '#555',
},
],
'places': [str(self.place.pk)],
}
@property
def update_expected(self):
tag_root = ActivityTag.objects.get(name='tag2')
self.assertIsNone(tag_root.event)
tag_bound = ActivityTag.objects.get(name='body')
self.assertEqual(tag_bound.event, self.event)
return {
'title': "act",
'is_public': False,
'remarks': "another test remark",
'tags': [tag_root, tag_bound],
'places': [self.place],
} }

View file

@ -16,6 +16,7 @@ event_router = NestedSimpleRouter(router, r'event', lookup='event')
event_router.register(r'place', views.PlaceViewSet) event_router.register(r'place', views.PlaceViewSet)
event_router.register(r'tag', views.ActivityTagViewSet) event_router.register(r'tag', views.ActivityTagViewSet)
event_router.register(r'template', views.ActivityTemplateViewSet) event_router.register(r'template', views.ActivityTemplateViewSet)
event_router.register(r'activity', views.ActivityViewSet)
# API URLconf: routers + auth for browsable API. # API URLconf: routers + auth for browsable API.

View file

@ -1 +0,0 @@
default_app_config = 'communication.apps.CommunicationConfig'

View file

@ -1,7 +1,4 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class CommunicationConfig(AppConfig): class CommunicationConfig(AppConfig):
name = 'communication' name = 'communication'
verbose_name = _("Communication")

View file

@ -20,10 +20,7 @@ class Subscription(models.Model):
class UserSubscription(Subscription): class UserSubscription(Subscription):
user = models.ForeignKey( user = models.ForeignKey(User)
User,
on_delete=models.CASCADE,
)
is_unsub = models.BooleanField( is_unsub = models.BooleanField(
_("désinscription"), _("désinscription"),
default=False default=False
@ -35,10 +32,7 @@ class UserSubscription(Subscription):
class GroupSubscription(Subscription): class GroupSubscription(Subscription):
group = models.ForeignKey( group = models.ForeignKey(Group)
Group,
on_delete=models.CASCADE,
)
class Meta: class Meta:
verbose_name = _("souscription en groupe") verbose_name = _("souscription en groupe")

View file

@ -1 +0,0 @@
default_app_config = 'equipment.apps.EquipmentConfig'

View file

@ -1,133 +0,0 @@
from django.contrib import admin
from django import forms
from .models import Equipment, EquipmentDefault, EquipmentRevision, EquipmentCategory, EquipmentLost, EquipmentAttributeValue, EquipmentAttribute, EquipmentStorage
from .fields import IdField, IdWidget
class IdForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if 'min_value' in kwargs:
kwargs.pop('min_value')
if 'num_choices' in kwargs:
num_choices = kwargs.pop('num_choices')
else:
num_choices = None
super(IdForm, self).__init__(*args, **kwargs)
for field in self.instance._meta.fields:
if isinstance(field, IdField):
if num_choices is None:
choices = []
else:
choices = [(k, str(k)) for k in range(1, num_choices+1)]
self.fields[field.name].choices = choices
self.fields[field.name].widget = IdWidget(choices=self.fields[field.name].choices)
class IdFormset(forms.models.BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
if self.instance:
kwargs["num_choices"] = self.instance.stock
return kwargs
class EquipmentRevisionExtraInline(admin.TabularInline):
model = EquipmentRevision
extra = 0
form = IdForm
formset = IdFormset
classes = ['collapse']
class EquipmentDefaultExtraInline(admin.TabularInline):
model = EquipmentDefault
extra = 0
form = IdForm
formset = IdFormset
classes = ['collapse']
class EquipmentLostExtraInline(admin.TabularInline):
model = EquipmentLost
extra = 0
form = IdForm
formset = IdFormset
classes = ['collapse']
class EquipmentAttributeValueInline(admin.TabularInline):
model = EquipmentAttributeValue
extra = 0
class CategoryAdmin(admin.ModelAdmin):
readonly_fields = ['full_name_p']
list_display = ['name', 'parent', "full_name_p"]
ordering = ['name', 'parent']
search_fields = ['name',]
autocomplete_fields = ['parent', ]
class StorageAdmin(admin.ModelAdmin):
list_display = ['name']
ordering = ['name']
search_fields = ['name']
class EquipmentAttributeAdmin(admin.ModelAdmin):
list_display = ['name']
ordering = ['name']
search_fields = ['name',]
class EquipmentAdmin(admin.ModelAdmin):
save_as_continue = True
save_on_top = True
autocomplete_fields = ['category', 'storage', ]
readonly_fields = ['full_category_p',
'added_at',
'modified_at',
'stock_aviable_p',
'ids_aviable_p',
'stock_lost_p',
'ids_lost_p',
]
list_display = ['name', 'stock', 'owner', 'category', 'storage', 'modified_at']
fieldsets = (
('Général', {
'fields': ('name', 'owner', 'stock', )
}),
('Info stock',
{
'fields': (
'stock_aviable_p',
'ids_aviable_p',
'stock_lost_p',
'ids_lost_p',
),
}),
('Attributs', {
'fields': ('category', 'full_category_p', 'storage'),
}),
('Description', {
'fields': ('description', 'added_at', 'modified_at',),
}),
)
ordering = ['name', 'owner', 'category']
inlines = [EquipmentAttributeValueInline,
EquipmentDefaultExtraInline,
EquipmentLostExtraInline,
EquipmentRevisionExtraInline]
search_fields = ['name', 'description',]
list_filter = ['owner', 'category', 'storage', ]
admin.site.register(Equipment, EquipmentAdmin)
admin.site.register(EquipmentCategory, CategoryAdmin)
admin.site.register(EquipmentStorage, StorageAdmin)
admin.site.register(EquipmentAttribute, EquipmentAttributeAdmin)

View file

@ -1,7 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class EquipmentConfig(AppConfig): class EquipmentConfig(AppConfig):
name = 'equipment' name = 'equipment'
verbose_name = _("Équipement")

View file

@ -1,77 +0,0 @@
from django.db import models
from django import forms
#class IdWidget(AdminMultipleChoiceFieldWidget):
class IdWidget(forms.widgets.CheckboxSelectMultiple):
template_name = 'equipment/widgets/checkbox_select.html'
option_template_name = 'equipment/widgets/checkbox_option.html'
def __init__(self, *args, **kwargs):
# nb_items = kwargs.pop('nb_items')
# kwargs['choices'] = list(range(1, nb_items+1))
super(IdWidget, self).__init__(*args, **kwargs)
class Media:
css = {
'all': ('css/idwidget.css',)
}
class IdFormField(forms.MultipleChoiceField):
#widget = IdWidget
def __init__(self, *args, **kwargs):
if 'min_value' in kwargs:
kwargs.pop('min_value')
if 'max_value' in kwargs:
kwargs.pop('max_value')
super(IdFormField, self).__init__(*args, **kwargs)
class IdField(models.BigIntegerField):
def parse_integer(self, n):
res = []
k = 1
while(n > 0):
if n & 1:
res.append(k)
n >>= 1
k += 1
return res
def from_db_value(self, value, expression, connection):
if value is None:
return value
return self.parse_integer(value)
def to_python(self, value):
if isinstance(value, list):
return value
if value is None:
return value
return self.parse_integer(value)
def get_prep_value(self, value):
res = 0
for b in value:
res |= 1 << (int(b)-1)
return res
def __init__(self, separator=",", *args, **kwargs):
self.separator = separator
super(IdField, self).__init__(*args, **kwargs)
self.validators = [] # TODO : validateurs pertinents
def deconstruct(self):
name, path, args, kwargs = super(IdField, self).deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs['separator'] = self.separator
return name, path, args, kwargs
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# while letting the caller override them.
defaults = {'form_class': IdFormField}
defaults.update(kwargs)
return super(IdField, self).formfield(**defaults)

View file

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-06 17:29 # Generated by Django 1.11.4 on 2017-08-12 12:47
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import equipment.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -34,7 +32,7 @@ class Migration(migrations.Migration):
name='EquipmentAttribution', name='EquipmentAttribution',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.BigIntegerField(verbose_name='quantité attribuée')), ('amount', models.PositiveSmallIntegerField(verbose_name='quantité attribuée')),
('remarks', models.TextField(verbose_name="remarques concernant l'attribution")), ('remarks', models.TextField(verbose_name="remarques concernant l'attribution")),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')), ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')), ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')),
@ -44,45 +42,12 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'attributions de matériel', 'verbose_name_plural': 'attributions de matériel',
}, },
), ),
migrations.CreateModel(
name='EquipmentCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='nom')),
],
options={
'verbose_name': 'catégories',
'verbose_name_plural': 'catégories',
},
),
migrations.CreateModel(
name='EquipmentOwner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='nom')),
],
options={
'verbose_name': 'propriétaire de matériel',
'verbose_name_plural': 'propriétaires de matériel',
},
),
migrations.CreateModel(
name='EquipmentPole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='nom')),
],
options={
'verbose_name': 'pôle',
'verbose_name_plural': 'pôle',
},
),
migrations.CreateModel( migrations.CreateModel(
name='EquipmentRemark', name='EquipmentRemark',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remark', models.TextField(verbose_name='remarque sur le matériel')), ('remark', models.TextField(verbose_name='remarque sur le matériel')),
('ids', equipment.fields.IdField()), ('amount', models.PositiveSmallIntegerField(verbose_name='quantité concernée')),
('is_broken', models.BooleanField()), ('is_broken', models.BooleanField()),
('is_lost', models.BooleanField()), ('is_lost', models.BooleanField()),
('equipment', models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')), ('equipment', models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')),
@ -92,43 +57,14 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'remarques sur le matériel', 'verbose_name_plural': 'remarques sur le matériel',
}, },
), ),
migrations.CreateModel(
name='EquipmentRevision',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='date')),
('remark', models.TextField(verbose_name='remarque sur la révision')),
('ids', equipment.fields.IdField()),
('equipment', models.ForeignKey(help_text='Matériel concerné par les révisions', on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='equipment.Equipment')),
],
options={
'verbose_name': 'révision de matériel',
'verbose_name_plural': 'révisions de matériel',
},
),
migrations.AddField( migrations.AddField(
model_name='equipment', model_name='equipment',
name='activities', name='activities',
field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'), field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'),
), ),
migrations.AddField(
model_name='equipment',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
),
migrations.AddField( migrations.AddField(
model_name='equipment', model_name='equipment',
name='event', name='event',
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
), ),
migrations.AddField(
model_name='equipment',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentOwner'),
),
migrations.AddField(
model_name='equipment',
name='pole',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentPole'),
),
] ]

View file

@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-07 16:58
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
import django.db.models.deletion
import equipment.fields
class Migration(migrations.Migration):
dependencies = [
('equipment', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='EquipmentDefault',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remark', models.TextField(verbose_name='remarque sur le défaut')),
('ids', equipment.fields.IdField()),
('is_unusable', models.BooleanField(verbose_name='inutilisable')),
('send_repare', models.BooleanField(verbose_name='à envoyer réparareur')),
('equipment', models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')),
],
options={
'verbose_name': 'defaut matériel',
'verbose_name_plural': 'défauts sur le matériel',
},
),
migrations.CreateModel(
name='EquipmentLost',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lost_at', models.DateField(default=datetime.date.today, verbose_name='perdu le')),
('ids', equipment.fields.IdField()),
('equipment', models.ForeignKey(help_text='Matériel concerné par la perte', on_delete=django.db.models.deletion.CASCADE, related_name='losts', to='equipment.Equipment')),
],
),
migrations.RemoveField(
model_name='equipmentremark',
name='equipment',
),
migrations.DeleteModel(
name='EquipmentRemark',
),
]

View file

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-07 18:43
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('equipment', '0002_auto_20180807_1658'),
]
operations = [
migrations.RemoveField(
model_name='equipment',
name='owner',
),
migrations.DeleteModel(
name='EquipmentOwner',
),
]

View file

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-07 18:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
('equipment', '0003_auto_20180807_1843'),
]
operations = [
migrations.AddField(
model_name='equipment',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
),
]

View file

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-08 10:13
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0004_equipment_owner'),
]
operations = [
migrations.RemoveField(
model_name='equipment',
name='pole',
),
migrations.AddField(
model_name='equipmentcategory',
name='parent',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
),
migrations.DeleteModel(
name='EquipmentPole',
),
]

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-08 13:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0005_auto_20180808_1013'),
]
operations = [
migrations.AlterField(
model_name='equipmentcategory',
name='parent',
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
),
]

View file

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-08 14:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0006_auto_20180808_1354'),
]
operations = [
migrations.CreateModel(
name='EquipmentAttribute',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True, verbose_name='nom')),
],
options={
'verbose_name': 'attribut',
'verbose_name_plural': 'attributs',
},
),
migrations.CreateModel(
name='EquipmentAttributeValue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=200, verbose_name='valeur')),
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentAttribute')),
('equipment', models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='attributes', to='equipment.Equipment')),
],
options={
'verbose_name': 'attribut de matériel',
'verbose_name_plural': 'attributs de matériel',
},
),
]

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-08 14:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0007_equipmentattribute_equipmentattributevalue'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentCategory'),
),
]

View file

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-09 12:00
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('equipment', '0008_auto_20180808_1454'),
]
operations = [
migrations.AddField(
model_name='equipment',
name='added_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2018, 8, 9, 12, 0, 50, 140250, tzinfo=utc), verbose_name='ajouté le'),
preserve_default=False,
),
migrations.AddField(
model_name='equipment',
name='modified_at',
field=models.DateTimeField(auto_now=True, verbose_name='dernière modification'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-09 12:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('equipment', '0009_auto_20180809_1200'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='stock',
field=models.PositiveSmallIntegerField(verbose_name='quantité totale'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-09 14:05
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('equipment', '0010_auto_20180809_1211'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='description',
field=models.TextField(blank=True, verbose_name='description'),
),
]

View file

@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-20 11:24
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0011_auto_20180809_1405'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentCategory', verbose_name='catégorie'),
),
migrations.AlterField(
model_name='equipment',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group', verbose_name='propriétaire'),
),
migrations.AlterField(
model_name='equipmentattributevalue',
name='attribute',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentAttribute', verbose_name='attribut'),
),
migrations.AlterField(
model_name='equipmentattributevalue',
name='equipment',
field=models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='attributes', to='equipment.Equipment', verbose_name='matériel'),
),
migrations.AlterField(
model_name='equipmentattribution',
name='equipment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment', verbose_name='matériel'),
),
migrations.AlterField(
model_name='equipmentcategory',
name='parent',
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='equipment.EquipmentCategory', verbose_name='parent'),
),
migrations.AlterField(
model_name='equipmentdefault',
name='equipment',
field=models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment', verbose_name='matériel'),
),
migrations.AlterField(
model_name='equipmentlost',
name='equipment',
field=models.ForeignKey(help_text='Matériel concerné par la perte', on_delete=django.db.models.deletion.CASCADE, related_name='losts', to='equipment.Equipment', verbose_name='matériel'),
),
migrations.AlterField(
model_name='equipmentrevision',
name='equipment',
field=models.ForeignKey(help_text='Matériel concerné par les révisions', on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='equipment.Equipment', verbose_name='matériel'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-26 17:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('equipment', '0012_auto_20180820_1124'),
]
operations = [
migrations.AlterField(
model_name='equipmentattribution',
name='remarks',
field=models.TextField(blank=True, verbose_name="remarques concernant l'attribution"),
),
]

View file

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-26 22:05
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0013_auto_20180826_1949'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.Group', verbose_name='propriétaire'),
),
migrations.AlterField(
model_name='equipmentcategory',
name='parent',
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='equipment.EquipmentCategory', verbose_name='parent'),
),
]

View file

@ -1,26 +0,0 @@
# Generated by Django 2.1.5 on 2019-03-18 11:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('equipment', '0014_auto_20180827_0005'),
]
operations = [
migrations.CreateModel(
name='EquipmentStorage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='nom')),
],
),
migrations.AddField(
model_name='equipment',
name='storage',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentStorage', verbose_name='stockage'),
),
]

View file

@ -1,17 +0,0 @@
# Generated by Django 2.1.5 on 2019-03-18 11:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('equipment', '0015_auto_20190318_1245'),
]
operations = [
migrations.AlterModelOptions(
name='equipmentstorage',
options={'verbose_name': 'stockage', 'verbose_name_plural': 'stockages'},
),
]

View file

@ -1,98 +1,7 @@
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group
from event.models import Activity, EventSpecificMixin from event.models import Activity, EventSpecificMixin
from django.db.models import Q
from .fields import IdField
from datetime import date
class EquipmentStorage(models.Model):
name = models.CharField(
_("nom"),
max_length=200,
)
class Meta:
verbose_name = _("stockage")
verbose_name_plural = _("stockages")
def __str__(self):
return self.name
class EquipmentCategory(models.Model):
name = models.CharField(
_("nom"),
max_length=200,
)
parent = models.ForeignKey(
'self',
blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
related_name="children",
help_text=_("merci de ne pas faire de référence cyclique"),
verbose_name=_("parent"),
)
def has_parent(self, cat):
current = self
for k in range(100):
if current is None:
return False
if current == cat:
return True
current = current.parent
def full_name(self):
current = self
res = ""
for k in range(100):
res = "/{current}{old}".format(
current=current.name,
old=res)
if current.parent is None:
break
current = current.parent
return res
full_name.short_description = _("Chemin complet")
full_name_p = property(full_name)
class Meta:
verbose_name = _("catégories")
verbose_name_plural = _("catégories")
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if self.pk:
done = False
current = self
while not done:
if current.parent == self:
self.parent = None
done = True
elif current.parent is None:
done = True
current = current.parent
return super().save(*args, **kwargs)
class EquipmentQuerySet(models.QuerySet):
def in_category(self, cat):
filtre = Q(id__lt=0)
childs_id = [c.id for c in EquipmentCategory.objects.all()
if c.has_parent(cat)]
for pk in childs_id:
filtre |= Q(category__id=pk)
return self.filter(filtre)
class Equipment(EventSpecificMixin, models.Model): class Equipment(EventSpecificMixin, models.Model):
@ -100,98 +9,13 @@ class Equipment(EventSpecificMixin, models.Model):
_("nom du matériel"), _("nom du matériel"),
max_length=200, max_length=200,
) )
stock = models.PositiveSmallIntegerField(_("quantité totale")) stock = models.PositiveSmallIntegerField(_("quantité disponible"))
description = models.TextField( description = models.TextField(_("description"))
_("description"),
blank=True,
)
activities = models.ManyToManyField( activities = models.ManyToManyField(
Activity, Activity,
related_name="equipment", related_name="equipment",
through="EquipmentAttribution", through="EquipmentAttribution",
) )
owner = models.ForeignKey(
Group,
verbose_name=_("propriétaire"),
blank=True,
null=True,
on_delete=models.SET_NULL,
)
category = models.ForeignKey(
EquipmentCategory,
verbose_name=_("catégorie"),
on_delete=models.PROTECT,
)
storage = models.ForeignKey(
EquipmentStorage,
blank=True,
null=True,
default=None,
verbose_name=_("stockage"),
on_delete=models.PROTECT,
)
added_at = models.DateTimeField(
_("ajouté le"),
auto_now_add=True,
)
modified_at = models.DateTimeField(
_("dernière modification"),
auto_now=True,
)
objects = EquipmentQuerySet.as_manager()
def is_in_category(self, cat):
current = self.category
for k in range(100):
if current is None:
return False
if current == cat:
return True
current = current.parent
def ids_aviable(self):
if self.stock is None:
return []
res = list(map(lambda x: x+1, range(self.stock)))
for lost in self.losts.all():
res = [x
for x in res
if x not in lost.ids]
# TODO cassé
# TODO utilisés
return res
def ids_lost(self):
res = []
for lost in self.losts.all():
res = res + [x
for x in lost.ids
if x not in res]
return res
def stock_aviable(self):
aviable = self.ids_aviable()
return len(aviable)
def stock_lost(self):
return len(self.ids_lost())
def full_category(self):
return self.category.full_name()
full_category.short_description = _("Chemin complet")
ids_aviable.short_description = _("disponibles")
ids_lost.short_description = _("perdus")
stock_aviable.short_description = _("quantité disponible")
stock_lost.short_description = _("quantité perdue")
full_category_p = property(full_category)
ids_aviable_p = property(ids_aviable)
ids_lost_p = property(ids_lost)
stock_aviable_p = property(stock_aviable)
stock_lost_p = property(stock_lost)
class Meta: class Meta:
verbose_name = _("matériel") verbose_name = _("matériel")
@ -201,63 +25,11 @@ class Equipment(EventSpecificMixin, models.Model):
return self.name return self.name
class EquipmentAttribute(models.Model):
name = models.CharField(
_("nom"),
max_length=200,
unique=True,
)
class Meta:
verbose_name = _("attribut")
verbose_name_plural = _("attributs")
def __str__(self):
return self.name
class EquipmentAttributeValue(models.Model):
equipment = models.ForeignKey(
Equipment,
on_delete=models.CASCADE,
related_name="attributes",
verbose_name=_("matériel"),
help_text=_("Matériel concerné par le defaut"),
)
attribute = models.ForeignKey(
EquipmentAttribute,
verbose_name=_("attribut"),
on_delete=models.CASCADE,
)
value = models.CharField(
_("valeur"),
max_length=200,
)
class Meta:
verbose_name = _("attribut de matériel")
verbose_name_plural = _("attributs de matériel")
def __str__(self):
return "{attr}={value}".format(attr=self.attribute.name,
value=self.value)
class EquipmentAttribution(models.Model): class EquipmentAttribution(models.Model):
equipment = models.ForeignKey( equipment = models.ForeignKey(Equipment)
Equipment, activity = models.ForeignKey(Activity)
verbose_name=_("matériel"), amount = models.PositiveSmallIntegerField(_("quantité attribuée"))
on_delete=models.CASCADE, remarks = models.TextField(_("remarques concernant l'attribution"))
)
activity = models.ForeignKey(
Activity,
on_delete=models.CASCADE,
)
amount = models.BigIntegerField(_("quantité attribuée"))
remarks = models.TextField(
_("remarques concernant l'attribution"),
blank=True,
)
class Meta: class Meta:
verbose_name = _("attribution de matériel") verbose_name = _("attribution de matériel")
@ -265,72 +37,30 @@ class EquipmentAttribution(models.Model):
def __str__(self): def __str__(self):
return "%s (%d) -> %s" % (self.equipment.name, return "%s (%d) -> %s" % (self.equipment.name,
self.amount, self.amout,
self.activity.get_herited('title')) self.activity.get_herited('title'))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if (self.equipment.event if self.equipment.event and self.equipment.event != self.activity.event:
and self.equipment.event != self.activity.event):
raise ValidationError raise ValidationError
super(EquipmentAttribution, self).save(*args, **kwargs) super(EquipmentAttribution, self).save(*args, **kwargs)
class EquipmentDefault(models.Model): class EquipmentRemark(models.Model):
remark = models.TextField(_("remarque sur le défaut")) remark = models.TextField(_("remarque sur le matériel"))
equipment = models.ForeignKey( equipment = models.ForeignKey(
Equipment, Equipment,
verbose_name=_("matériel"),
on_delete=models.CASCADE,
related_name="remarks", related_name="remarks",
help_text=_("Matériel concerné par le defaut"), help_text=_("Matériel concerné par la remarque"),
) )
ids = IdField() amount = models.PositiveSmallIntegerField(_("quantité concernée"))
is_unusable = models.BooleanField(_("inutilisable")) is_broken = models.BooleanField()
send_repare = models.BooleanField(_("à envoyer réparareur")) is_lost = models.BooleanField()
class Meta: class Meta:
verbose_name = _("defaut matériel") verbose_name = _("remarque sur matériel")
verbose_name_plural = _("défauts sur le matériel") verbose_name_plural = _("remarques sur le matériel")
def __str__(self):
return "%s : %s" % (self.equipment.name,
self.remark)
class EquipmentLost(models.Model):
lost_at = models.DateField(
_("perdu le"),
default=date.today,
)
equipment = models.ForeignKey(
Equipment,
verbose_name=_("matériel"),
on_delete=models.CASCADE,
related_name="losts",
help_text=_("Matériel concerné par la perte"),
)
ids = IdField()
class EquipmentRevision(models.Model):
date = models.DateField(
_("date"),
default=date.today,
)
equipment = models.ForeignKey(
Equipment,
verbose_name=_("matériel"),
on_delete=models.CASCADE,
related_name="revisions",
help_text=_("Matériel concerné par les révisions"),
)
remark = models.TextField(_("remarque sur la révision"))
ids = IdField()
class Meta:
verbose_name = _("révision de matériel")
verbose_name_plural = _("révisions de matériel")
def __str__(self): def __str__(self):
return "%s : %s" % (self.equipment.name, return "%s : %s" % (self.equipment.name,

View file

@ -1,12 +0,0 @@
.nice_select input[type="checkbox"] {
display: none;
}
.nice_select input[type="checkbox"]:checked + label {
background: red;
color:white;
}
.nice_select ul {
display: inline-block;
}

View file

@ -1,109 +0,0 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group
from django.db.models import Q
from django.http.request import QueryDict
import django_filters
from django_filters.widgets import LinkWidget
from django_tables2.utils import A
import django_tables2 as tables
from .models import Equipment, EquipmentCategory
class EquipmentFilter(django_filters.FilterSet):
owner = django_filters.ModelChoiceFilter(
field_name='owner',
queryset=Group.objects.all(),
widget=LinkWidget(),
)
category = django_filters.ModelChoiceFilter(
field_name='category',
queryset=EquipmentCategory.objects.all(),
widget=LinkWidget(),
method='filter_category',
)
def filter_category(self, queryset, name, value):
return queryset.in_category(value)
def get_categories(self, qs):
"""
rend les catégories qui servent à filtrer les Equipments de qs
ie les catégories des equipments et tous leurs parents
"""
filtre = Q(id__lt=0)
for eq in qs:
current = eq.category
for k in range(100):
if current is None:
break
filtre |= Q(id=current.id)
current = current.parent
return EquipmentCategory.objects.filter(filtre)
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
# On que les requêtes vides rendent quelque chose
if data is None:
data = QueryDict('category&owner')
super().__init__(data=data, queryset=queryset,
request=request, prefix=prefix)
if self.queryset is not None:
for filter_ in self.filters.values():
if filter_.queryset.model == Group:
own_ids = [eq.owner.id for eq in self.queryset
if eq.owner is not None]
filtre = Q(id__lt=0)
for own_id in own_ids:
filtre |= Q(id=own_id)
filter_.queryset = Group.objects.filter(filtre)
if filter_.queryset.model == EquipmentCategory:
filter_.queryset = self.get_categories(self.queryset)
class Meta:
model = Equipment
fields = ['category', 'owner']
class AbstractEquipmentTable(tables.Table):
stock_aviable_p = tables.Column(
accessor=A('stock_aviable_p'),
orderable=False, # TODO le rendre ordorable
verbose_name=_("Quantité disponible"),
)
full_category_p = tables.Column(
accessor=A('full_category_p'),
order_by=('category'),
verbose_name=_("Catégorie"),
)
name = tables.LinkColumn(
'equipment:detail',
args=[A('pk')],
verbose_name=_("Matériel"),
)
admin = tables.LinkColumn(
'admin:equipment_equipment_change',
attrs={
'a': {'class': 'glyphicon glyphicon-cog'}
},
text="",
orderable=False,
args=[A('pk')],
verbose_name=_(""),
)
def before_render(self, request):
if (request.user.is_staff and
request.user.has_perm('equipment_change_equipment')):
self.columns.show('admin')
else:
self.columns.hide('admin')
class EquipmentTable(AbstractEquipmentTable):
class Meta:
model = Equipment
template_name = 'equipment/tables/bootstrap-responsive.html'
fields = ['name', 'stock', 'owner', ]
sequence = ['admin', 'name', 'stock', 'stock_aviable_p',
'full_category_p', 'owner', ]

View file

@ -1,13 +0,0 @@
{% extends "shared/base.html" %}
{% load i18n staticfiles %}
{% block title %}{% trans "Matériel" %}{% endblock %}
{% block content %}
<h2>{{ equipment.name }}<a class="pull-right glyphicon glyphicon-cog" href="{% url 'admin:equipment_equipment_change' equipment.id %}"></a></h2>
{% endblock %}
{% block aside %}
Coucou :)
{% endblock %}

View file

@ -1,50 +0,0 @@
{% extends "shared/base.html" %}
{% load i18n staticfiles %}
{% block title %}{% trans "Matériel" %}{% endblock %}
{% block content %}
<h1 class="equipment">{% trans "Inventaire" %}</h1>
<div class="module-list">
<a href="{% url 'equipment:list' %}" class="module equipment">
<span class="glyphicon glyphicon-list-alt"></span>
{% trans "Tout le matériel" %}
</a>
<a href="#TODO" class="module equipment">
<span class="glyphicon glyphicon-list-alt"></span>
{% trans "Disponible" %}
</a>
</div>
<h2 class="staff">{% trans "Liste par Propriétaire" %}</h2>
<div class="module-list">
{% for owner in owners %}
<a href="{% url 'equipment:list_by_owner' owner.id %}" class="module staff">
<span class="glyphicon glyphicon-user"></span>
{{ owner.name }}
</a>
{% endfor %}
</div>
<h2 class="equipment">{% trans "Liste par Catégorie" %}</h2>
<div class="tree">
<ul>
{% for node in root_cat %}
{% include "equipment/tree_cat.html" %}
{% endfor %}
</ul>
</div>
{% endblock %}
{% block aside %}
<div class="heading">
{{ nb_type }} <span class="sub">{% trans "référence" %}{{ nb_type|pluralize}}</span>
</div>
<div class="heading separator">
{{ stock }} <span class="sub">{% trans "item" %}{{ stock|pluralize}}</span>
</div>
<div class="heading inverted small">
{{ categories.count }} <span class="sub">{% trans "catégorie" %}{{ categories.count|pluralize}}</span>
</div>
<div class="heading inverted small ">
{{ owners.count }} <span class="sub">{% trans "propriéaire" %}{{ owners.count|pluralize}}</span>
</div>
{% endblock %}

View file

@ -1,30 +0,0 @@
{% extends "shared/base.html" %}
{% load render_table from django_tables2 %}
{% load bootstrap3 %}
{% load i18n staticfiles %}
{% block title %}{% trans "Matériel" %}{% endblock %}
{% block content %}
<h1 class="equipment">Inventaire</h1>
{% if subtitle %}
<h2 class="equipment">{{ subtitle }}</h2>
{% endif %}
{% render_table table %}
{% endblock %}
{% block aside %}
<div class="heading">
{{ nb_type }} <span class="sub">{% trans "référence" %}{{ nb_type|pluralize}}</span>
</div>
<div class="heading separator">
{{ stock }} <span class="sub">{% trans "item" %}{{ stock|pluralize}}</span>
</div>
{% if filter %}
<div class="text inverted">
<form id="filter_form" action="" method="get" class="form form-inline">
{% bootstrap_form filter.form layout='horizontal' size='lg' %}
</form>
</div>
{% endif %}
{% endblock %}

View file

@ -1,16 +0,0 @@
{% extends 'equipment/tables/bootstrap.html' %}
{% block table-wrapper %}
<div class="table-container table-responsive">
{% block table %}
{{ block.super }}
{% endblock table %}
{% if table.page and table.paginator.num_pages > 1 %}
{% block pagination %}
{{ block.super }}
{% endblock pagination %}
{% endif %}
</div>
{% endblock table-wrapper %}

View file

@ -1,103 +0,0 @@
{% load django_tables2 %}
{% load i18n %}
{% block table-wrapper %}
<div class="table-container">
{% block table %}
<table {% render_attrs table.attrs class="table table-striped" %}>
{% block table.thead %}
{% if table.show_header %}
<thead>
<tr>
{% for column in table.columns %}
<th {{ column.attrs.th.as_html }}>
{% if column.orderable %}
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a>
{% else %}
{{ column.header }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
{% if table.has_footer %}
<tfoot>
<tr>
{% for column in table.columns %}
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
{% endblock table.tfoot %}
</table>
{% endblock table %}
{% if table.page and table.paginator.num_pages > 1 %}
{% block pagination %}
<nav aria-label="Table navigation">
<ul class="pagination">
{% if table.page.has_previous %}
{% block pagination.previous %}
<li class="previous">
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">
<span aria-hidden="true">&laquo;</span>
{% trans 'previous' %}
</a>
</li>
{% endblock pagination.previous %}
{% endif %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
{% for p in table.page|table_page_range:table.paginator %}
<li {% if p == table.page.number %}class="active"{% endif %}>
{% if p == '...' %}
<a href="#">{{ p }}</a>
{% else %}
<a href="{% querystring table.prefixed_page_field=p %}">
{{ p }}
</a>
{% endif %}
</li>
{% endfor %}
{% endblock pagination.range %}
{% endif %}
{% if table.page.has_next %}
{% block pagination.next %}
<li class="next">
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">
{% trans 'next' %}
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endblock pagination.next %}
{% endif %}
</ul>
</nav>
{% endblock pagination %}
{% endif %}
</div>
{% endblock table-wrapper %}

View file

@ -1,11 +0,0 @@
<li> <a href="{% url "equipment:list_by_category" node.id %}"><span class="category_node">{{node.name}}</span></a>
{%if node.children %}
<ul>
{%for ch in node.children.all %}
{%with node=ch template_name="equipment/tree_cat.html" %}
{%include template_name%}
{%endwith%}
{%endfor%}
</ul>
{%endif%}
</li>

View file

@ -1 +0,0 @@
{% include "django/forms/widgets/input.html" %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{{ widget.label }}</label>

View file

@ -1,5 +0,0 @@
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }} nice_select"{% endif %} style="display: inline-block">{% for group, options, index in widget.optgroups %}{% if group %}
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
<li style="list-style-type: none;">{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
</ul></li>{% endif %}{% endfor %}
</ul>{% endwith %}

View file

@ -1,11 +0,0 @@
from django.conf.urls import url
from .views import EquipmentList, EquipmentView, EquipmentListByCategory, EquipmentListByOwner, EquipmentHome
app_name = 'equipment'
urlpatterns = [
url(r'^$', EquipmentHome.as_view(), name='home'),
url(r'^all/$', EquipmentList.as_view(), name='list'),
url(r'^(?P<pk>[0-9]+)/$', EquipmentView.as_view(), name='detail'),
url(r'^c/(?P<pk>[0-9]+)/$', EquipmentListByCategory.as_view(), name='list_by_category'),
url(r'^o/(?P<pk>[0-9]+)/$', EquipmentListByOwner.as_view(), name='list_by_owner'),
]

View file

@ -1,97 +0,0 @@
from .models import Equipment, EquipmentCategory
from django.contrib.auth.models import Group
from django.db.models import Sum
from django.views.generic import DetailView, ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from django_filters.views import FilterView
from django_tables2.views import SingleTableMixin
from .tables import EquipmentTable, EquipmentFilter
class EquipmentHome(LoginRequiredMixin, ListView):
template_name = 'equipment/home.html'
context_object_name = 'categories'
model = EquipmentCategory
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# TODO remplacer par les vrais owners
context['owners'] = Group.objects.all()
categories = (EquipmentCategory.objects.order_by('name')
.prefetch_related('children'))
context['root_cat'] = categories.filter(parent=None)
queryset = Equipment.objects.all()
context['stock'] = queryset.aggregate(Sum('stock'))['stock__sum']
context['nb_type'] = queryset.count()
return context
class EquipmentListAbstract(LoginRequiredMixin, SingleTableMixin,FilterView):
table_class = EquipmentTable
filterset_class = EquipmentFilter
template_name = 'equipment/list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['stock'] = self.queryset.aggregate(Sum('stock'))['stock__sum']
context['nb_type'] = self.queryset.count()
return context
class EquipmentList(EquipmentListAbstract):
def get_queryset(self):
self.queryset = Equipment.objects.all()
return self.queryset
class EquipmentListByCategory(EquipmentListAbstract):
def get_category(self):
try:
pk = self.kwargs.get('pk')
except KeyError:
raise AttributeError(
"View %s must be called with an object "
"pk in the URLconf." % self.__class__.__name__
)
return EquipmentCategory.objects.get(id=pk)
def get_queryset(self):
cat = self.get_category()
self.queryset = Equipment.objects.all().in_category(cat)
return self.queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cat = self.get_category()
context['subtitle'] = "Dans {cat}".format(cat=cat.full_name())
return context
class EquipmentListByOwner(EquipmentListAbstract):
def get_owner(self):
try:
pk = self.kwargs.get('pk')
except KeyError:
raise AttributeError(
"View %s must be called with an object "
"pk in the URLconf." % self.__class__.__name__
)
return Group.objects.get(id=pk)
def get_queryset(self):
owner = self.get_owner()
self.queryset = Equipment.objects.filter(owner=owner)
return self.queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
owner = self.get_owner()
context['subtitle'] = "Matériel de {owner}".format(owner=owner)
return context
class EquipmentView(LoginRequiredMixin, DetailView):
model = Equipment
template_name = 'equipment/detail.html'

View file

@ -2,6 +2,6 @@ import os
from channels.asgi import get_channel_layer from channels.asgi import get_channel_layer
if "DJANGO_SETTINGS_MODULE" not in os.environ: if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings")
channel_layer = get_channel_layer() channel_layer = get_channel_layer()

View file

@ -10,8 +10,6 @@ We also load the secrets in this file.
import os import os
from . import secret from . import secret
from django.urls import reverse_lazy
from django.contrib import messages
def import_secret(name): def import_secret(name):
@ -27,7 +25,6 @@ def import_secret(name):
SECRET_KEY = import_secret("SECRET_KEY") SECRET_KEY = import_secret("SECRET_KEY")
ADMINS = import_secret("ADMINS") ADMINS = import_secret("ADMINS")
SERVER_EMAIL = import_secret("SERVER_EMAIL")
DBNAME = import_secret("DBNAME") DBNAME = import_secret("DBNAME")
DBUSER = import_secret("DBUSER") DBUSER = import_secret("DBUSER")
@ -47,46 +44,31 @@ BASE_DIR = os.path.dirname(
INSTALLED_APPS = [ INSTALLED_APPS = [
# 'shared.apps.CustomAdminConfig', 'communication.apps.CommunicationConfig',
'equipment.apps.EquipmentConfig',
'event.apps.EventConfig',
'users.apps.UsersConfig',
'shared.apps.SharedConfig',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites', 'channels',
'rest_framework',
# 'channels',
# 'rest_framework',
'bootstrapform', 'bootstrapform',
'widget_tweaks', 'widget_tweaks',
'taggit', 'api',
'django_tables2',
'django_filters',
'bootstrap3',
'allauth_ens',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth_cas',
'allauth_ens.providers.clipper',
# 'api',
'communication',
'equipment',
'event',
'shared',
'users',
] ]
MIDDLEWARE = [ MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
@ -99,7 +81,7 @@ REST_FRAMEWORK = {
'TEST_REQUEST_DEFAULT_FORMAT': 'json', 'TEST_REQUEST_DEFAULT_FORMAT': 'json',
} }
ROOT_URLCONF = 'poulpe.urls' ROOT_URLCONF = 'evenementiel.urls'
STATIC_URL = "/static/" STATIC_URL = "/static/"
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
@ -137,19 +119,19 @@ DATABASES = {
} }
} }
# CHANNEL_LAYERS = { CHANNEL_LAYERS = {
# "default": { "default": {
# "BACKEND": "asgi_redis.RedisChannelLayer", "BACKEND": "asgi_redis.RedisChannelLayer",
# "CONFIG": { "CONFIG": {
# "hosts": [( "hosts": [(
# "redis://:{passwd}@{host}:{port}/{db}" "redis://:{passwd}@{host}:{port}/{db}"
# .format(passwd=REDIS_PASSWD, host=REDIS_HOST, .format(passwd=REDIS_PASSWD, host=REDIS_HOST,
# port=REDIS_PORT, db=REDIS_DB) port=REDIS_PORT, db=REDIS_DB)
# )], )],
# }, },
# "ROUTING": "poulpe.routing.channel_routing", "ROUTING": "evenementiel.routing.channel_routing",
# } }
# } }
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
@ -166,55 +148,12 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'fr-fr' LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Paris' TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True
USE_TZ = True USE_TZ = True
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
SITE_ID = 1
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
CAS_VERSION = "2"
CAS_IGNORE_REFERER = True
CAS_LOGIN_MSG = None
CAS_REDIRECT_URL = reverse_lazy('shared:home')
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
LOGIN_URL = reverse_lazy('account_login')
LOGOUT_URL = reverse_lazy('account_logout')
LOGIN_REDIRECT_URL = reverse_lazy('shared:home')
ACCOUNT_HOME_URL = reverse_lazy('shared:home')
ACCOUNT_DETAILS_URL = reverse_lazy('shared:home')
SOCIALACCOUNT_PROVIDERS = {
# …
'clipper': {
# These settings control whether a message containing a link to
# disconnect from the CAS server is added when users log out.
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT': True,
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT_LEVEL': messages.INFO,
},
}
ACCOUNT_ADAPTER = 'shared.allauth_adapter.AccountAdapter'
#SOCIALACCOUNT_ADAPTER='allauth_ens.adapter.LongTermClipperAccountAdapter'
SOCIALACCOUNT_ADAPTER= 'shared.allauth_adapter.SocialAccountAdapter'

View file

@ -11,12 +11,10 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEBUG = True DEBUG = True
# Add some debugging tools # Add some debugging tools
INSTALLED_APPS += ["debug_toolbar", ] # NOQA INSTALLED_APPS += ["debug_toolbar", "debug_panel"] # NOQA
MIDDLEWARE = ( MIDDLEWARE_CLASSES = (
[ ["debug_panel.middleware.DebugPanelMiddleware"]
"debug_toolbar.middleware.DebugToolbarMiddleware", + MIDDLEWARE_CLASSES # NOQA
]
+ MIDDLEWARE # NOQA
) )

View file

@ -18,6 +18,6 @@ DATABASES = {
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "asgiref.inmemory.ChannelLayer", "BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "poulpe.routing.channel_routing", "ROUTING": "evenementiel.routing.channel_routing",
}, },
} }

View file

@ -5,23 +5,11 @@ from django.conf import settings
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
#from django_cas_ng import views as django_cas_views
from allauth_ens.views import capture_login, capture_logout
urlpatterns = [ urlpatterns = [
url(r'^admin/login/$', capture_login),
url(r'^admin/logout/$', capture_logout),
url(r'^compte/', include('allauth.urls')),
# Admin
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
# Apps
url(r'^equipment/', include('equipment.urls')),
url(r'^event/', include('event.urls')), url(r'^event/', include('event.urls')),
#url(r'^user/', include('users.urls')), url(r'^user/', include('users.urls')),
# REST
url(r'^api/', include('api.urls')), url(r'^api/', include('api.urls')),
# Reste
url(r'^', include('shared.urls')), url(r'^', include('shared.urls')),
] ]

View file

@ -1,5 +1,5 @@
""" """
WSGI config for GestionEvenementiel project. WSGI config for evenementiel project.
It exposes the WSGI callable as a module-level variable named ``application``. It exposes the WSGI callable as a module-level variable named ``application``.
@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings")
application = get_wsgi_application() application = get_wsgi_application()

View file

@ -1 +0,0 @@
default_app_config = 'event.apps.EventConfig'

View file

@ -1,97 +1,3 @@
from django.contrib import admin from django.contrib import admin
from .models import Event, Place, ActivityTag, Activity, ActivityTemplate # TODO add me # Register your models here.
from equipment.models import EquipmentAttribution
class EquipmentAttributionExtraInline(admin.TabularInline):
autocomplete_fields = ['equipment', ]
model = EquipmentAttribution
extra = 0
classes = ['collapse']
class EventAdmin(admin.ModelAdmin):
list_display = ['title', 'slug', 'beginning_date', 'ending_date']
readonly_fields = ['created_by', 'created_at', ]
ordering = ['title', 'beginning_date', 'ending_date', ]
search_fields = ['title', 'decription', ]
list_filter = ['beginning_date', 'ending_date', ]
date_hierarchy = 'beginning_date'
class PlaceAdmin(admin.ModelAdmin):
list_display = ['name', 'event', ]
ordering = ['name', 'event', ]
search_fields = ['name', ]
list_filter = ['event', ]
class ActivityTagAdmin(admin.ModelAdmin):
list_display = ['name', 'event', 'is_public', ]
ordering = ['name', 'event', 'is_public', ]
search_fields = ['name', ]
list_filter = ['event', 'is_public', ]
class ActivityTemplateAdmin(admin.ModelAdmin):
save_as_continue = True
save_on_top = True
list_display = ['name', 'title', 'event', 'is_public', ]
ordering = ['name', 'title', 'event', 'has_perm', ]
search_fields = ['name', 'title', 'description', 'remark', ]
list_filter = ['event', 'is_public', 'has_perm', 'tags', ]
filter_horizontal = ['tags', 'places', ]
fieldsets = (
('Identifiant', {
'fields': ('name', ),
}),
('Général', {
'fields': ('event', 'title', 'is_public', 'places', ),
'description': "Tous ces champs sont héritables (Sauf Évènement)",
}),
('Permanences', {
'fields': ('has_perm', ('min_perm', 'max_perm', ), ),
'classes': ('collapse',),
'description': "Tous ces champs sont héritables",
}),
('Descriptions', {
'fields': ('description', 'tags', 'remarks', ),
'classes': ('collapse',),
'description': "Tous ces champs sont héritables",
}),
)
class ActivityAdmin(admin.ModelAdmin):
save_as = True
save_on_top = True
list_display = ['title', 'event', 'is_public', 'parent', ]
ordering = ['title', 'event', 'has_perm', 'parent', ]
search_fields = ['title', 'description', 'remark', ]
list_filter = ['event', 'is_public', 'has_perm', 'tags', ]
filter_horizontal = ['tags', 'places', 'staff', ]
inlines = [EquipmentAttributionExtraInline, ]
fieldsets = (
('Général', {
'fields': ('event', 'parent', 'title', 'is_public', 'beginning', 'end', 'places', ),
'description': "Tous ces champs sont héritables (sauf parent et Évènement)",
}),
('Permanences', {
'fields': ('has_perm', ('min_perm', 'max_perm', ), 'staff', ),
'classes': ('wide',),
'description': "Tous ces champs sont héritables (sauf les gens en perm)",
}),
('Descriptions', {
'fields': ('description', 'tags', 'remarks', ),
'classes': ('collapse',),
'description': "Tous ces champs sont héritables",
}),
)
admin.site.register(Event, EventAdmin)
admin.site.register(Place, PlaceAdmin)
admin.site.register(ActivityTag, ActivityTagAdmin)
admin.site.register(ActivityTemplate, ActivityTemplateAdmin)
admin.site.register(Activity, ActivityAdmin)

View file

@ -1,7 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class EventConfig(AppConfig): class EventConfig(AppConfig):
name = 'event' name = 'event'
verbose_name = _("Évènement")

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2018-08-06 07:51 # Generated by Django 1.11.4 on 2017-08-12 12:47
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
@ -74,8 +74,8 @@ class Migration(migrations.Migration):
('slug', models.SlugField(help_text="Seulement des lettres, des chiffres ou les caractères '_' ou '-'.", unique=True, verbose_name='identificateur')), ('slug', models.SlugField(help_text="Seulement des lettres, des chiffres ou les caractères '_' ou '-'.", unique=True, verbose_name='identificateur')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='date de création')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='date de création')),
('description', models.TextField(verbose_name='description')), ('description', models.TextField(verbose_name='description')),
('beginning_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de début')), ('beginning_date', models.DateTimeField(verbose_name='date de début')),
('ending_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de fin')), ('ending_date', models.DateTimeField(verbose_name='date de fin')),
('created_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='créé par')), ('created_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='créé par')),
], ],
options={ options={

View file

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-20 15:29
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('event', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='activitytemplate',
name='name',
field=models.CharField(default='change_me!', help_text='Ne sera pas affiché', max_length=200, verbose_name='Nom du template'),
preserve_default=False,
),
migrations.AlterField(
model_name='activity',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AlterField(
model_name='activitytemplate',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
]

View file

@ -1,5 +1,5 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.exceptions import FieldDoesNotExist, FieldError, ValidationError from django.core.exceptions import FieldDoesNotExist, FieldError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -123,6 +123,7 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model):
Event, Event,
verbose_name=_("évènement"), verbose_name=_("évènement"),
on_delete=models.CASCADE, on_delete=models.CASCADE,
editable=False,
) )
is_public = models.NullBooleanField( is_public = models.NullBooleanField(
_("est public"), _("est public"),
@ -166,34 +167,12 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model):
class ActivityTemplate(AbstractActivityTemplate): class ActivityTemplate(AbstractActivityTemplate):
name = models.CharField(
_("Nom du template"),
max_length=200,
help_text=_("Ne sera pas affiché"),
)
class Meta: class Meta:
verbose_name = _("template activité") verbose_name = _("template activité")
verbose_name_plural = _("templates activité") verbose_name_plural = _("templates activité")
def __str__(self): def __str__(self):
return self.name return self.title
def clean(self):
errors = []
# On clean les nombre de permanents
if not self.has_perm:
self.max_perm = None
self.min_perm = None
else:
if self.min_perm > self.max_perm:
errors.append(ValidationError(
_("Nombres de permanents incompatibles"),
code='wrong-nb-perm',
))
if errors != []:
raise ValidationError(errors)
class Activity(AbstractActivityTemplate): class Activity(AbstractActivityTemplate):
@ -214,86 +193,6 @@ class Activity(AbstractActivityTemplate):
beginning = models.DateTimeField(_("heure de début")) beginning = models.DateTimeField(_("heure de début"))
end = models.DateTimeField(_("heure de fin")) end = models.DateTimeField(_("heure de fin"))
def clean(self):
errors = []
# On clean les nombre de permanents
if not self.get_herited('has_perm'):
self.max_perm = None
self.min_perm = None
else:
if self.get_herited('min_perm') > self.get_herited('max_perm'):
errors.append(ValidationError(
_("Nombres de permanents incompatibles"),
code='wrong-nb-perm',
))
# On valide l'héritage
for f in self._meta.get_fields():
try:
# On réccupère le field du parent
attrname = f.name
tpl_field = ActivityTemplate._meta.get_field(attrname)
# Peut-être que ce n'est pas un field
# concerné par l'héritage
except FieldDoesNotExist:
continue
# Y'a certains champs dont on se moque
if attrname in ['id', 'staff', 'tags', ]:
continue
# C'est plus compliqué que ça pour les nb_perm
if attrname in ['max_perm', 'min_perm', ]:
if not self.get_herited('has_perm'):
continue
# On a un Many to Many, on lit différement
if tpl_field.many_to_many:
pass
# # On a pas spécifié
# if not value.exists():
# # On a pas de parent
# if self.parent is None:
# errors.append(ValidationError(
# _("N'hérite pas d'un template, spécifier le champs : %(attr)s"),
# code='bad-overriding',
# params={'attr': f.verbose_name},
# ))
# else:
# pvalue = getattr(self.parent, attrname)
# # On a un parent qui ne dit rien
# if not pvalue.exists():
# errors.append(ValidationError(
# _("Champs non précisé chez le parent, spécifier : %(attr)s"),
# code='bad-overriding',
# params={'attr': f.verbose_name},
# ))
else:
value = getattr(self, attrname)
# On a pas spécifié
if value is None:
# On a pas de parent
if self.parent is None:
errors.append(ValidationError(
_("N'hérite pas d'un template, spécifier le champs : %(attr)s"),
code='bad-overriding',
params={'attr': f.verbose_name},
))
else:
pvalue = getattr(self.parent, attrname)
# On a un parent qui ne dit rien
if pvalue is None:
errors.append(ValidationError(
_("Champs non précisé chez le parent, spécifier : %(attr)s"),
code='bad-overriding',
params={'attr': f.verbose_name},
))
if errors != []:
raise ValidationError(errors)
def get_herited(self, attrname): def get_herited(self, attrname):
try: try:
tpl_field = ActivityTemplate._meta.get_field(attrname) tpl_field = ActivityTemplate._meta.get_field(attrname)
@ -308,9 +207,9 @@ class Activity(AbstractActivityTemplate):
if tpl_field.many_to_many: if tpl_field.many_to_many:
if value.exists(): if value.exists():
return value return value
elif self.parent is not None: else:
return getattr(self.parent, attrname) return getattr(self.parent, attrname)
elif value is None and self.parent is not None: elif value is None:
return getattr(self.parent, attrname) return getattr(self.parent, attrname)
else: else:
return value return value

View file

@ -1,373 +0,0 @@
/* Calendar */
#cal-container {
width: 100%;
height: 100%;
padding: 0;
min-height: 80vh;
display: block;
position: relative;
line-height: 100%;
}
#cal-container,
#cal-container * {
box-sizing: border-box;
}
/* Time slots */
#cal-container .cal-time-slot-container {
display: grid;
grid-template-rows: 30px auto;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100% + 30px + 10px);
padding: 0;
z-index: 10;
}
#cal-container .cal-time-slot {
border-right: 1px solid #EEE;
background-color: #FAFAFA;
}
#cal-container .cal-time-slot:nth-child(even) {
background-color: #F4F4F4;
}
#cal-container .cal-time-slot:last-child {
border-right: 0;
}
#cal-container .cal-time-slot-hour {
padding: 0 0 0 calc(100% - 1.4rem + 7px);
background-color: #F9F9F9;/* #3D3D3D; */
border-bottom: 1px solid #AAA;
color: #333;
font-size: 1.4rem;
line-height: 30px;
vertical-align: middle;
}
#cal-container .cal-time-slot-hour:nth-child(even) {
background-color: #FCFCFC;/* #464646; */
}
#cal-container .cal-time-slot-hour:first-child {
color: transparent;
border-right: 0;
}
#cal-container .cal-time-slot-hour.cal-last-hour {
padding: 0;
color: transparent;
}
#cal-container .cal-time-slot.cal-last-hour,
#cal-container .cal-time-slot-hour.cal-last-hour {
border-right: 1px solid #AAA;
}
#cal-container .cal-time-slot.cal-first-hour,
#cal-container .cal-time-slot-hour.cal-first-hour {
border-left: 1px solid #AAA;
}
/* Events */
#cal-container .cal-event-container {
display: grid;
/* grid-template-columns: repeat(24, 1fr); */
/* grid-template-rows: repeat(12, auto); */
position: absolute;
top: 40px;
left: 0;
width: 100%;
height: 100%;
padding: 0;
z-index: 100;
}
#cal-container .cal-event {
position: relative;
height: 80px;
margin: 2px 0;
/* padding: 5px; */
/* background-color: #EFEFEF; */
border-radius: 3px;
/* border: 1px solid #CCC; */
border-width: 1px;
border-style: solid;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* z-index: 500; */
transition: 50ms ease-in;
}
#cal-container .cal-event:hover {
/* background-color: #FEDDDD; */
}
#cal-container .cal-event > * {
display: none;
margin: 5px;
}
#cal-container .cal-event > .cal-event-name,
#cal-container .cal-event > .cal-event-location,
#cal-container .cal-event > .cal-event-perm-count {
display: block;
}
#cal-container .cal-event > .cal-event-name {
font-weight: bold;
}
#cal-container .cal-event > .cal-event-location {
font-style: italic;
}
#cal-container .cal-event > .cal-event-perm-count {
position: absolute;
bottom: 0;
right: 0;
}
#cal-container .cal-event:not(.cal-event-subscribed) > .cal-event-perm-count.cal-perms-missing {
width: calc(100% - 10px);
right: auto;
margin: 5px;
padding: 5px;
background-color: #FFF;
border: 2px solid #E44;
color: #E44;
font-weight: bold;
border-radius: 3px;
overflow: hidden;
}
#cal-container .cal-event.cal-event-subscribed {
border-width: 3px;
border-color: #000;
}
#cal-container .cal-event.cal-event-subscribed::after {
content: "✔";
position: absolute;
left: 0;
bottom: 0;
width: 16px;
height: 16px;
padding: 1px;
color: #fff;
background-color: #000;
border-top-right-radius: 3px;
}
/* Event details popup */
#cal-container .cal-event-details {
position: absolute;
min-height: 100px;
/* min-width: 40%; */
max-width: 80%;
padding: 20px;
background-color: #333;
color: #FFF;
border-radius: 4px;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6);
z-index: 1000;
}
#cal-container .cal-event-details:after {
bottom: 100%;
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: #333;
border-width: 20px;
margin-left: -20px;
}
#cal-container .cal-event-details.above-event:after {
top: 100%;
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-top-color: #333;
border-width: 20px;
margin-left: -20px;
}
#cal-container .cal-event-details * {
z-index: 1000;
}
#cal-container .cal-event-details .cal-detail-close-button {
width: 35px;
height: 35px;
position: absolute;
top: 10px;
right: 10px;
margin: 0;
padding: 5px;
background: transparent;
border: none;
border-radius: 50%;
font-size: 1.6rem;
color: #BBB;
transition: 100ms ease-out;
}
#cal-container .cal-event-details .cal-detail-close-button:hover {
background-color: #484848;
color: #EFEFEF;
}
#cal-container .cal-event-details a,
#cal-container .cal-event-details a:hover {
color: #FFF;
text-decoration: none;
}
#cal-container .cal-event-details .cal-detail-name > h3 {
margin: 0 0 25px 26px;
padding: 10px;
border-radius: 4px;
color: #FFF;
font-size: 2.5rem;
text-transform: uppercase;
text-align: center;
}
#cal-container .cal-event-details .cal-detail-name > h3::after {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin: 0 0 0 10px;
vertical-align: middle;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAA90lEQVQoz2WRsS6DURiGn/yaaNqBySpGicFg6iJcAYuuNBaJpJEg6gLcADFIdWR0BZZKS4SBRTe3YFf/Y/j/c3LUe4Zz3vd78+U734uUJ7Ptg7k68NDpoIdyU9V3TzzwRdXt1HCq9pwR510W616oZ8Gwoe6KWFN1ScStogui3sRJ9uxYLd/n6hTuaCy3XHfNWuR6hM+OojBQdSXyJ0cZizwSsMo3kEc+ZCEjxZgxE8j4oBFZhRww8gaff4fEL3UuGfK4uG5L4VLVfvrNCrDJHfd0gSrXQB2AJvu0+Cm8HbXnbGw9seqwWH2z65Wv/8MKcfdVHaZx/wKtOg5kifzQhwAAAABJRU5ErkJggg==);
opacity: 0.6;
}
#cal-container .cal-event-details .cal-detail-name:hover > h3 {
margin-right: 26px;
margin-bottom: 10px;
background-color: #484848;
color: #FFF;
}
#cal-container .cal-event-details .cal-detail-name:hover > h3::after {
content: "Cliquez pour afficher les détails.";
display: block;
width: auto;
height: 15px;
padding: 2px 0 0 0;
font-size: 1.2rem;
background-image: none;
color: #DDD;
}
#cal-container .cal-event-details .cal-detail-name:hover + .cal-detail-close-button {
opacity: 0;
}
#cal-container .cal-event-details table {
margin: 0 auto;
font-size: 1.8rem;
}
#cal-container .cal-event-details td.cal-detail-label {
padding: 0 10px 10px 0;
font-weight: bold;
text-align: right;
}
#cal-container .cal-event-details td.cal-detail-value {
padding: 0 0 10px 10px;
text-align: left;
}
#cal-container .cal-event-details .cal-detail-perm-area {
margin: 10px 0;
padding: 10px;
background-color: #DFDFDF;
color: #333;
text-align: center;
border-radius: 4px;
}
#cal-container .cal-event-details .cal-detail-perm-title {
display: block;
margin: 0 0 10px 0;
}
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count {
margin: 0 10px 0 0;
font-size: 2.5rem;
vertical-align: middle;
}
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-missing {
color: #E44;
}
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-full {
color: #393;
}
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-subscription-switch {
margin: 0 0 0 10px;
padding: 10px;
font-size: 1.8rem;
vertical-align: middle;
}
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-nb-missing-perms {
margin: 20px 0 0 0;
padding: 5px;
background-color: #FFF;
border-radius: 4px;
text-align: center;
font-size: 1.5rem;
color: #E44;
font-weight: bold;
}
#cal-container .cal-event-details .cal-detail-description {
margin: 20px 0 20px 0;
color: #DDD;
font-size: 1.8rem;
font-style: italic;
text-align: justify;
line-height: 120%;
}
#cal-container .cal-event-details .cal-detail-tag {
display: inline-block;
margin: 5px;
padding: 5px;
border: 1px solid #DDD;
}

View file

@ -1,108 +0,0 @@
/* Tipso Bubble Styles */
.tipso_bubble, .tipso_bubble > .tipso_arrow{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.tipso_bubble {
position: absolute;
text-align: center;
border-radius: 6px;
z-index: 9999;
}
.tipso_style{
/* cursor: help; */
border-bottom: 1px dotted;
}
.tipso_title {
padding: 3px 0;
border-radius: 6px 6px 0 0;
font-weight: 700;
}
.tipso_content {
word-wrap: break-word;
padding: 0.5em;
}
/* Tipso Bubble size classes - Similar to Foundation's syntax*/
.tipso_bubble.tiny {
font-size: 0.6rem;
}
.tipso_bubble.small {
font-size: 0.8rem;
}
.tipso_bubble.default {
font-size: 1rem;
}
.tipso_bubble.large {
font-size: 1.2rem;
width: 100%;
}
.tipso_bubble.cal_small {
font-size: 1.6rem;
}
/* Tipso Bubble Div */
.tipso_bubble > .tipso_arrow{
position: absolute;
width: 0; height: 0;
border: 8px solid;
pointer-events: none;
}
.tipso_bubble.top > .tipso_arrow {
border-top-color: #000;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
top: 100%;
left: 50%;
margin-left: -8px;
}
.tipso_bubble.bottom > .tipso_arrow {
border-bottom-color: #000;
border-right-color: transparent;
border-left-color: transparent;
border-top-color: transparent;
bottom: 100%;
left: 50%;
margin-left: -8px;
}
.tipso_bubble.left > .tipso_arrow {
border-left-color: #000;
border-top-color: transparent;
border-bottom-color: transparent;
border-right-color: transparent;
top: 50%;
left: 100%;
margin-top: -8px;
}
.tipso_bubble.right > .tipso_arrow {
border-right-color: #000;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
top: 50%;
right: 100%;
margin-top: -8px;
}
.tipso_bubble .top_right_corner,
.tipso_bubble.top_right_corner {
border-bottom-left-radius: 0;
}
.tipso_bubble .bottom_right_corner,
.tipso_bubble.bottom_right_corner {
border-top-left-radius: 0;
}
.tipso_bubble .top_left_corner,
.tipso_bubble.top_left_corner {
border-bottom-right-radius: 0;
}
.tipso_bubble .bottom_left_corner,
.tipso_bubble.bottom_left_corner {
border-top-right-radius: 0;
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
$(function(){
function initEnrolment(elt) {
elt = $(elt);
elt.find("form.enrolment").on("submit", function() {
elt.addClass("sending-request");
var form = this;
var url = form.action + "?ajax";
$.post(url, $(form).serialize(), function(data) {
elt.html(data);
elt.removeClass("sending-request");
initEnrolment(elt);
});
return false;
});
}
$.each($(".activity-summary"), function(i, item) { initEnrolment(item) });
});

View file

@ -1,84 +0,0 @@
// Interval graph coloring algorithm, by Twal
class IntervalColoration {
constructor (intervals) {
this.intervals = intervals;
this.n = this.intervals.length;
this.computeInterferenceGraph();
this.computePEO();
this.computeColoration();
}
computeInterferenceGraph() {
this.adj = new Array(this.n);
for (let i = 0; i < this.n; ++i) {
this.adj[i] = [];
}
for (let i = 0; i < this.n; ++i) {
for (let j = 0; j < i; ++j) {
let inti = this.intervals[i];
let intj = this.intervals[j];
if (inti[0] < intj[1] && intj[0] < inti[1]) {
this.adj[i].push(j);
this.adj[j].push(i);
}
}
}
}
//Perfect elimination order using Maximum Cardinality Search
//Runs in O(n^2), could be optimized in O(n log n)
computePEO() {
let marked = new Array(this.n);
let nbMarkedNeighbor = new Array(this.n);
this.perm = new Array(this.n);
for (let i = 0; i < this.n; ++i) {
marked[i] = false;
nbMarkedNeighbor[i] = 0;
}
for (let k = this.n-1; k >= 0; --k) {
let maxi = -1;
for (let i = 0; i < this.n; ++i) {
if (!marked[i] && (maxi == -1 || nbMarkedNeighbor[i] >= nbMarkedNeighbor[maxi])) {
maxi = i;
}
}
for (let i = 0; i < this.adj[maxi].length; ++i) {
nbMarkedNeighbor[this.adj[maxi][i]] += 1;
}
this.perm[maxi] = k;
marked[maxi] = true;
}
// console.log(this.perm);
}
computeColoration() {
this.colors = new Array(this.n);
let isColorUsed = new Array(this.n);
for (let i = 0; i < this.n; ++i) {
this.colors[i] = -1;
isColorUsed[i] = false;
}
for (let i = 0; i < this.n; ++i) {
let ind = this.perm[i];
for (let j = 0; j < this.adj[ind].length; ++j) {
let neigh = this.adj[ind][j];
if (this.colors[neigh] >= 0) {
isColorUsed[this.colors[neigh]] = true;
}
}
for (let j = 0; j < this.n; ++j) {
if (!isColorUsed[j]) {
this.colors[ind] = j;
break;
}
}
for (let j = 0; j < this.adj[ind].length; ++j) {
let neigh = this.adj[ind][j];
if (this.colors[neigh] >= 0) {
isColorUsed[this.colors[neigh]] = false;
}
}
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,52 +0,0 @@
{% extends "shared/base.html" %}
{% load i18n staticfiles event_tags %}
{% load render_table from django_tables2 %}
{% block title %}{% trans "Activity" %}{% endblock %}
{% block content %}
<h1>{{ activity.title}}
{% if user.is_staff %}
<a class='glyphicon glyphicon-cog pull-right' href='{% url "admin:event_activity_change" activity.id %}'></a>
{% endif %}
</h1>
{% include "event/activity_summary.html" with activity=activity %}
<h2>Description</h2>
<p><strong>Description</strong>{{activity.description|default:"&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;"}}</p>
<p><strong>Remarque (staff)</strong>{{activity.remark|default:"&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;"}}</p>
<button class="collapsible active"><h3>Matériel</h3></button>
<div class="content fluid">
<table class="table table-responsive table-striped">
<tr>
<th>Matériel</th>
<th>Quantité</th>
<th>Propriétaire</th>
<th>Remarque</th>
</tr>
{% for att in attributions %}
<tr>
<td><a href="{% url 'equipment:detail' att.equipment.id %}">
{{ att.equipment }}</a></td>
<td>{{ att.amount }}</td>
<td>{{ att.equipment.owner }}</td>
<td>{{ att.remark }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}
{% block aside %}
<div class="text">
Du
<strong>
{{ activity.beginning | date:"l d F Y H:i" }}
</strong>
au
<strong>
{{ activity.end | date:"l d F Y H:i" }}
</strong>
</p>
</div>
{% endblock %}

View file

@ -1,79 +0,0 @@
{% load i18n event_tags %}
{% with activity|get_herited:'has_perm' as has_perm %}
<table class="table table-responsive table-striped">
<tr>
<td>
<span class="glyphicon glyphicon-tree-deciduous"></span>
{% with activity|get_herited:'places' as places %}
{% if places.all %}
<span>{{ places.all |join:", "}}</span>
{% else %}
<span>&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;</span>
{% endif %}
{% endwith %}
</td>
<td>
<strong>public</strong>
&nbsp;:&nbsp;
{% with activity|get_herited:'is_public' as is_public %}
<span class="glyphicon {{ is_public|yesno:"yes glyphicon-ok-sign, no glyphicon-remove-sign, dunno glyphicon-question-sign"}}"></span>
{% endwith %}
</td>
<tr>
</tr>
<td>
<span class="glyphicon glyphicon-duplicate"></span>
{% if activity.parent %}
<span>{{ activity.parent}}</span>
{% else %}
<span>&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;</span>
{% endif %}
</td>
<td>
<strong>perm</strong>
&nbsp;:&nbsp;
<span class="glyphicon {{ has_perm|yesno:"yes glyphicon-ok-sign, no glyphicon-remove-sign, dunno glyphicon-question-sign"}}"></span>
</td>
<tr>
</tr>
<td>
<span class="glyphicon glyphicon-tag"></span>
{% with activity|get_herited:'tags' as tags %}
{% if tags.all %}
<span>{{ tags.all |join:", "}}</span>
{% else %}
<span>&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;</span>
{% endif %}
{% endwith %}
</td>
<td>
{% if has_perm %}
{{ activity|get_herited:'min_perm' }}
&le;
<strong>{{ activity.staff.count }}</strong>
&le;
{{ activity|get_herited:'max_perm' }}
{% endif %}
</td>
</tr>
</table>
{% if has_perm %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-8">
<strong>En perm : </strong>
{% with activity.staff.all as staff %}
{% if staff %}
<span>{{ staff |join:", "}}</span>
{% else %}
<span>&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;</span>
{% endif %}
{% endwith %}
</div>
<div class="col-sm-4">
{% enrol_btn activity request.user %}
</div>
</div>
</div>
{% endif %}
{% endwith %}

View file

@ -1,2 +1,25 @@
{% extends "shared/base.html" %} {% extends "base.html" %}
{% block sidenav %}
<div class="centered">
<h5 class="centered banner-text">La Nuit 2017</h5>
</div>
<li>
<a href="index.html">
<i class="fa fa-dashboard"></i>
<span>Looool</span>
</a>
</li>
<li class="sub-menu">
<a href="javascript:;" >
<i class="fa fa-desktop"></i>
<span>Prout</span>
</a>
<ul class="sub">
<li><a href="general.html">Lolilol</a></li>
<li><a href="buttons.html">Lorem</a></li>
<li><a href="panels.html">Ipsum</a></li>
</ul>
</li>
{% endblock %}

View file

@ -1,116 +0,0 @@
{% extends "shared/fluid.html" %}
{% load i18n staticfiles event_tags %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static "css/tipso.css" %}">
<link rel="stylesheet" href="{% static "css/calendar.css" %}">
<style>
#cal-toggle-unsubscribed-events-display {
display: block;
position: fixed;
bottom: 30px;
left: 30px;
border-bottom: 2px solid rgb(150, 50, 50);
font-size: 1.6rem;
font-weight: bold;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4),
0 1px 1px rgba(150, 50, 50, 0.7);
text-shadow: 0 0 7px rgb(150, 50, 50);
z-index: 5000;
}
</style>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript" src="{% static "js/tipso.min.js" %}"></script>
<script type="text/javascript" src="{% static "js/interval_coloration.js" %}"></script>
<script type="text/javascript" src="{% static "js/calendar.js" %}"></script>
<script type="text/javascript">
$(document).ready(() => {
let calendar = new Calendar({
startDate: new Date(2018, 10, 30, 8),
endDate: new Date(2018, 11, 2, 6),
eventDetailURLFormat: "https://cof.ens.fr/poulpe/event/activity/999999",
subscriptionURLFormat: "{% url "event:enrol_activity" 999999 %}?ajax=json",
csrfToken: $(".planning [name=csrfmiddlewaretoken]").val(),
groupEventsByLocation: true
});
// TODO: move this elsewhere
// Button to switch between:
// - displaying all events (default);
// - only displaying events for which the current user is enroled.
// Create the button
let toggleUnsubscribedEventDisplayButton = $("<button>")
.attr("type", "button")
.attr("id", "cal-toggle-unsubscribed-events-display")
.addClass("btn btn-primary")
.appendTo(calendar.containerNode);
// Set/update its label
function updateToggleButtonLabel () {
if (calendar.onlyDisplaySubscribedEvents) {
toggleUnsubscribedEventDisplayButton.html("Afficher toutes les activités");
}
else {
toggleUnsubscribedEventDisplayButton.html("Afficher seulement mes permanences");
}
}
updateToggleButtonLabel();
// Switch between display modes on click
toggleUnsubscribedEventDisplayButton.on("click", () => {
calendar.toggleEventsNotSubscribedByUser();
updateToggleButtonLabel();
});
// DEBUG: js console helpers, to be removed
console.log(calendar);
window["cal"] = calendar;
});
</script>
{% endblock %}
{% block content %}
<div class="planning">
{% csrf_token %}
{% regroup activities by beginning|date:"Y-m-d" as days_list %}
<div class="content fluid" id="cal-container">
{% for day in days_list %}
{% for activity in day.list %}
<div class="cal-event">
<span class="cal-event-id">{{ activity.id }}</span>
<span class="cal-event-name">{{ activity|get_herited:'title' }}</span>
<span class="cal-event-start-date">{{ activity.beginning | date:"j/m/Y H:i" }}</span>
<span class="cal-event-end-date">{{ activity.end | date:"j/m/Y H:i" }}</span>
{% with activity|get_herited:'places' as places %}
<span class="cal-event-location">{{ places.all | join:", " }}</span>
{% endwith %}
<span class="cal-event-description">{{ activity.description }}</span>
<span class="cal-event-url">{% url "event:activity" activity.id %}</span>
{% if activity|get_herited:'has_perm' %}
<span class="cal-event-has-perms">1</span>
<span class="cal-event-min-nb-perms">{{ activity|get_herited:'min_perm' }}</span>
<span class="cal-event-max-nb-perms">{{ activity|get_herited:'max_perm' }}</span>
<span class="cal-event-nb-perms">{{ activity.staff.count }}</span>
<span class="cal-event-subscribed">{% is_enrolled activity request.user %}</span>
{% endif %}
{% with activity|get_herited:'tags' as tags %}
{% for tag in tags.all %}
<span class="cal-event-tag">{{ tag.name }}</span>
{% endfor %}
{% endwith %}
</div>
{% endfor %}
{% endfor %}
</div>
{% endblock %}

View file

@ -1,101 +0,0 @@
{% extends "shared/base.html" %}
{% load i18n staticfiles event_tags %}
{% block title %}{% trans "Évènement" %}{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript" src="{% static "js/enrol_event.js" %}"></script>
{% endblock %}
{% block content %}
<h1>{{ event.title}}
{% if perms.event.event_can_change and user.is_staff %}
<a class='glyphicon glyphicon-cog pull-right' href='{% url "admin:event_event_change" event.id %}'></a>
{% endif %}
</h1>
<p>{{ event.description }}</p>
<h2>Boîte à outils</h2>
<div class="module-list">
<a href="#TODO" class="module">
<span class="glyphicon glyphicon-duplicate"></span>
Templates d'activité
</a>
<a href="#TODO" class="module">
<span class="glyphicon glyphicon-tag"></span>
Tags spécifiques
</a>
<a href="#todo" class="module">
<span class="glyphicon glyphicon-tree-deciduous"></span>
lieux spécifiques
</a>
<a href="{% url "event:calendar" event.slug %}" class="module">
Calendrier
</a>
{% if staffuser %}
<a href="{% url "event:event" event.slug %}" class="module">
Toutes les activités
</a>
{% else %}
<a href="{% url "event:event-staff" event.slug user.username %}" class="module">
Mes perms
</a>
{% endif %}
</div>
{% if staffuser %}
<h2>Perms de {{ staffuser.first_name }} {{ staffuser.last_name }}</h2>
{% else %}
<h2>Planning</h2>
{% endif %}
<div class="planning">
{% regroup activities by beginning|date:"Y-m-d" as days_list %}
{% for day in days_list %}
{% with day.list|first as f_act %}
<button class="collapsible active"><h3>{{ f_act.beginning|date:"l d F" }}</h3></button>
<div class="content fluid">
{% endwith %}
{% for activity in day.list %}
<div class="{% cycle "" "inverted" %} activity">
<div class="activity-title">
<h4>
{% if perms.event.activity_can_change and user.is_staff %}
<a class='glyphicon glyphicon-cog' href='{% url "admin:event_activity_change" activity.id %}'></a>
{% endif %}
<a href="{% url "event:activity" activity.id %}">
{{ activity|get_herited:'title' }}
</a>
</h4>
<span class="pull-right">
de <strong>{{ activity.beginning | time:"H:i" }}</strong>
à <strong>{{ activity.end| time:"H:i" }}</strong>
</span>
<div class="activity-summary">
{% include "event/activity_summary.html" with activity=activity %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
{% block aside %}
<div class="heading separator">
{{ activities.count }} <span class="sub">activité{{ activities.count|pluralize }}</span>
</div>
<div class="text inverted">
<p>Créé le {{ event.created_at | date:"l d F Y à H:i" }} par {{ event.created_by }}</p>
<p>
Du
<strong>
{{ event.beginning_date | date:"l d F Y H:i" }}
</strong>
au
<strong>
{{ event.ending_date | date:"l d F Y H:i" }}
</strong>
</p>
</div>
{% endblock %}

View file

@ -1,6 +0,0 @@
{% load i18n %}
<form method="POST" action="{% url "event:enrol_activity" activity.pk %}" class="enrolment {{ enrolled|yesno:"enrolled,unenrolled" }}">
{% csrf_token %}
<input type="hidden" name="goal" value="{{ enrolled|yesno:"unenrol,enrol" }}" />
{{ enrolled|yesno:_("Inscrit,") }} <input type="submit" value="{{ enrolled|yesno:_("Se désinscrire,S'inscrire") }}" class="btn btn-warning"/>
</form>

View file

@ -1,21 +0,0 @@
from django import template
register = template.Library()
@register.filter()
def get_herited(activity, attrname):
return activity.get_herited(attrname)
@register.inclusion_tag("event/tags/enrol_btn.html")
def enrol_btn(activity, user):
return {
"enrolled": activity.staff.filter(id=user.id).exists(),
"activity": activity,
}
@register.simple_tag
def is_enrolled(activity, user):
user_is_enrolled = activity.staff.filter(id=user.id).exists()
return "1" if user_is_enrolled else "0"

View file

@ -1,14 +1,7 @@
from django.conf.urls import url from django.conf.urls import url
from event.views import Index, EventView, EventViewStaff, ActivityView, EnrolActivityView, EventCalendar from event.views import Index
app_name = 'event' app_name = 'event'
urlpatterns = [ urlpatterns = [
# url(r'^$', Index.as_view(), name='index'), url(r'^$', Index.as_view(), name='index'),
url(r'^(?P<slug>[-\w]+)/$', EventView.as_view(), name='event'),
url(r'^(?P<slug>[-\w]+)/s/(?P<username>[-\w]+)/$', EventViewStaff.as_view(), name='event-staff'),
url(r'^(?P<slug>[-\w]+)/calendar/$', EventCalendar.as_view(), name='calendar'),
url(r'^activity/(?P<pk>[0-9]+)/$', ActivityView.as_view(),
name='activity'),
url(r'^activity/(?P<pk>[0-9]+)/enrol/$',
EnrolActivityView.as_view(), name="enrol_activity"),
] ]

View file

@ -1,97 +1,5 @@
from django.views.generic import TemplateView, DetailView, View from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404, render
from django.http import JsonResponse, HttpResponseRedirect
from django.urls import reverse
from .models import Event, Activity
from equipment.models import EquipmentAttribution
User = get_user_model()
class Index(TemplateView): class Index(TemplateView):
template_name = "event/index.html" template_name = "event/index.html"
class EventCalendar(LoginRequiredMixin, DetailView):
model = Event
template_name = 'event/calendar.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
event = self.object
context['activities'] = (Activity.objects.filter(event=event)
.order_by('beginning').prefetch_related(
'tags', 'places', 'staff', 'parent'))
return context
class EventView(LoginRequiredMixin, DetailView):
model = Event
template_name = 'event/event.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
event = self.object
context['activities'] = (Activity.objects.filter(event=event)
.order_by('beginning').prefetch_related(
'tags', 'places', 'staff', 'parent'))
return context
class EventViewStaff(LoginRequiredMixin, DetailView):
model = Event
template_name = 'event/event.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = User.objects.get(username=self.kwargs['username'])
event = self.object
context['staffuser'] = user
context['activities'] = (user.in_perm_activities
.filter(event=event)
.order_by('beginning')
.prefetch_related(
'tags', 'places', 'staff', 'parent')
)
return context
class ActivityView(LoginRequiredMixin, DetailView):
model = Activity
template_name = 'event/activity.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
activity = self.object
context['attributions'] = (EquipmentAttribution.objects
.filter(activity=activity)
.prefetch_related('equipment'))
return context
class EnrolActivityView(LoginRequiredMixin, View):
http_method_names = ['post']
def post(self, request, pk, *args, **kwargs):
activity = get_object_or_404(Activity, id=pk)
action = request.POST.get("goal", None)
success = True
if action == "enrol":
activity.staff.add(request.user)
elif action == "unenrol":
activity.staff.remove(request.user)
else:
success = False
if "ajax" in request.GET:
if request.GET["ajax"] == "json":
enrols = activity.staff
return JsonResponse({
"enrolled": enrols.filter(id=request.user.id).exists(),
"number": enrols.count(),
})
return render(request, "event/activity_summary.html",
{"activity": activity})
return HttpResponseRedirect(reverse("event:activity", kwargs={"pk":pk}))

View file

@ -3,7 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings.devlocal") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings.devlocal")
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

View file

View file

@ -10,7 +10,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPsd9jdssdJMds78"
REDIS_PASSWD="dummy" REDIS_PASSWD="dummy"
# It is used in quite a few places # It is used in quite a few places
SETTINGS="poulpe.settings.dev" SETTINGS="evenementiel.settings.dev"
# Fills a "templated file" with the information specified in the variables above # Fills a "templated file" with the information specified in the variables above
# e.g. every occurrence of {{DBUSER}} in the file will be replaced by the value # e.g. every occurrence of {{DBUSER}} in the file will be replaced by the value
@ -90,9 +90,9 @@ redis-cli -a $REDIS_PASSWD CONFIG REWRITE
cd /vagrant cd /vagrant
# Setup the secrets # Setup the secrets
sudo -H -u vagrant cp poulpe/settings/secret_example.py \ sudo -H -u vagrant cp evenementiel/settings/secret_example.py \
poulpe/settings/secret.py evenementiel/settings/secret.py
fill_template poulpe/settings/secret.py fill_template evenementiel/settings/secret.py
# Run the usual django admin commands # Run the usual django admin commands
function venv_python { function venv_python {

View file

@ -10,7 +10,7 @@ TimeoutSec=300
WorkingDirectory=/vagrant WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE={{SETTINGS}}" Environment="DJANGO_SETTINGS_MODULE={{SETTINGS}}"
ExecStart=/home/vagrant/venv/bin/daphne -u /srv/GE/GE.sock \ ExecStart=/home/vagrant/venv/bin/daphne -u /srv/GE/GE.sock \
poulpe.asgi:channel_layer evenementiel.asgi:channel_layer
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -1,3 +1,4 @@
-r requirements.txt -r requirements.txt
django-debug-toolbar django-debug-toolbar
django-debug-panel
ipython ipython

View file

@ -1,6 +0,0 @@
-r requirements.txt
# Production specific
daphne==1.3.0
psycopg2
gunicorn

View file

@ -1,14 +1,14 @@
asgi-redis==1.3.0 Django==1.11.*
asgiref==1.1.1 psycopg2
django-bootstrap3==10.0.1 asgi-redis
channels==1.1.5 Pillow
Django==2.1 channels
django-tables2==2.0.0a2 django-bootstrap-form==3.2.1
django-filter==2.0.0 django-widget-tweaks
git+https://git.eleves.ens.fr/klub-dev-ens/django-allauth-ens.git@1.1.3 djangorestframework==3.6.3
django-bootstrap-form==3.4
drf-nested-routers==0.90.0 drf-nested-routers==0.90.0
django-notifications==0.1.dev0 django-notifications
django-contrib-comments==1.8.0 django-contrib-comments
django-taggit==0.22.2
Pillow==5.0.0 # Production specific
daphne

View file

@ -1 +0,0 @@
default_app_config = 'shared.apps.SharedConfig'

View file

@ -1,34 +1,3 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import AdminSite
from django.contrib.sites.admin import SiteAdmin
from django.contrib.sites.models import Site
from django.contrib.auth import get_user_model # Register your models here.
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin, GroupAdmin
User = get_user_model()
class CustomAdminSite(AdminSite):
site_header = "Administration du Poulpe"
site_title = "Poulpe"
index_title = "Administration"
def index(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
# Move last app to the top of `app_list`.
# TODO fournir un bon ordre
app_list = self.get_app_list(request)
app_list.insert(0, app_list.pop(-1))
extra_context['app_list'] = app_list
return super().index(request, extra_context)
# admin.site = CustomAdminSite(name='admin')
admin.site.register(User, UserAdmin)
# admin.site.register(Group, GroupAdmin)
# admin.site.register(Site, SiteAdmin)

View file

@ -1,20 +0,0 @@
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
return False
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin):
# sociallogin.account is a SocialAccount instance.
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py
if sociallogin.account.provider == 'clipper':
return True
# It returns AccountAdapter.is_open_for_signup().
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/adapter.py
return super().is_open_for_signup(request, sociallogin)

View file

@ -1,10 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.contrib.admin.apps import AdminConfig
class SharedConfig(AppConfig): class SharedConfig(AppConfig):
name = 'shared' name = 'shared'
class CustomAdminConfig(AdminConfig):
default_site = 'shared.admin.CustomAdminSite'

View file

@ -11,7 +11,6 @@ class EventSpecificMixin(models.Model):
verbose_name=_("évènement"), verbose_name=_("évènement"),
help_text=_("Si spécifié, l'instance du modèle" help_text=_("Si spécifié, l'instance du modèle"
"est spécifique à l'évènement en question"), "est spécifique à l'évènement en question"),
on_delete=models.CASCADE,
blank=True, blank=True,
null=True null=True
) )

View file

@ -1,25 +0,0 @@
require 'compass/import-once/activate'
# Require any additional compass plugins here.
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "css"
sass_dir = "sass"
images_dir = "images"
javascripts_dir = "javascripts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# If you prefer the indented syntax, you might want to regenerate this
# project again passing --syntax sass, or you can uncomment this:
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass

View file

@ -1,114 +0,0 @@
//@import "compass/css3";
//Variables here:
//(alongside with commented suggestions)
$foreground-color:#b85b3f;//black;
$background-color:#e8e3c7;//white
$shadow-color:#ba9186;//$foreground-color;
$distance:8px;
$cut-distance:3px;//$distance/4;
$strips-size:6px; //10px
$strips-ratio:50%;//70%
$strips-angle:45deg;//90deg;
//cray stuff yo. be sure to try (if you please)
$animate:false;//true
$fixed:false;//true
body{
font-family: 'Open Sans Condensed', sans-serif;
font-size:85pt;
background-color:$background-color;
text-align:center;
line-height:1.2em;
padding-top:70px;
}
.dashed-shadow{
position:relative;
top:$distance;
left:$distance;
display:inline-block;
color:$shadow-color;
}
.dashed-shadow:before{
content:" ";
display:block;
position:absolute;
$bleeding-horizontal:10px;
$bleeding-vertical:0px;
top:-$bleeding-vertical - $distance;
left:-$bleeding-vertical - $distance;
bottom:-$bleeding-horizontal + $distance;
right:-$bleeding-horizontal + $distance;
z-index:1;
$color:$background-color;
$size:$strips-ratio/2;
$halfSize:$size/2;
$p1:$halfSize;
$p2:50%-$halfSize;
$p3:50%+$halfSize;
$p4:100%-$halfSize;
$transparent:transparentize($color,1);
@include background-image(linear-gradient($strips-angle,$color $p1, $transparent $p1, $transparent $p2,$color $p2, $color $p3, $transparent $p3, $transparent $p4, $color $p4));
background-size:$strips-size $strips-size;
@if($animate){
animation:dash-animation 30s infinite linear;
}
@if($fixed){
background-attachment:fixed;
}
}
.dashed-shadow:hover:before{
animation:dash-animation 30s infinite linear;
}
.dashed-shadow:after{
z-index:2;
content:attr(data-text);
position:absolute;
left:-$distance;
top:-$distance;
color:$foreground-color;
text-shadow:$cut-distance $cut-distance $background-color;
}
//fancy stuff - just useless fluff, don't mind from here onwards
.hello{
font-family:'Cookie',cursive;
font-size:140pt;
}
.sorta-block{
font-size:50pt;
line-height:1.1em;
@include transform(skew(0,-5deg));
z-index:3;
position:relative;
margin-top:20px;
margin-bottom:10px;
}
.sorta{
border-top:4px solid $foreground-color;
border-bottom:4px solid $foreground-color;
text-transform:uppercase;
z-index:3;
//position:relative;
//display:block;
//width:300px;
font-style:italic;
}
.hipsterish{
font-family: 'Sancreek', cursive;
font-size:70pt;
}
.dashed-shadow-text{
font-size:140pt;
line-height:0.7em;
//left:-10px;
}
.shadow{
font-size:120pt;
line-height:0.8em;
}

View file

@ -1,18 +0,0 @@
// main: global.scss
#filter_form {
.form-group {
.col-md-3, .col-md-9 {
float: none;
}
ul.form-control {
padding-left: 15px;
list-style: none;
height: auto;
a.selected {
text-decoration: underline;
color: darken($main_soft_color, 40%);
}
}
}
}

View file

@ -1,122 +0,0 @@
// main: global.scss
@mixin active {
&:active,
&.active {
@content;
}
}
@mixin hover-focus {
&:focus,
&.focus,
&:hover {
@content;
}
}
@mixin disabled {
&.disabled,
&[disabled],
fieldset[disabled] & {
@content;
}
}
@mixin dropdown-open {
.open > &.dropdown-toggle { @content }
}
@mixin btn-special {
/**
* This mixin applies content if the button is in at least one of the
* following states:
*
* - hovered,
* - focused,
* - actived,
* - is responsible of an opened dropdown.
*
* Where possible, state is checked from class attribute and
* :pseudo-classes.
*
* ## Bootstrap compatibility
*
* If content defines 'color', 'background-color' and 'border', it is safe
* to use this mixin with Bootstrap buttons as it will overrides all
* Bootstrap color defaults of the previous cases.
* To be precise, this covers all special important-like cases of the
* Bootstrap mixin 'button-variant' (except the 'disabled' case).
*
*/
@include hover-focus { @content }
@include active {
@content;
@include hover-focus { @content }
}
@include dropdown-open {
@content;
@include hover-focus { @content }
}
}
@mixin button-variant-2modes($color, $background-base, $background-special, $border) {
/**
* This mixins allows defining color-related properties of buttons.
*
* It sets the following properties:
* color: $color, except for disabled-like buttons.
* border-color: $border.
* background-color: Depending on button state:
* - Default, disabled:
* $background-base
* - Hovered, focused, actived, responsible of an opened dropdown:
* (one is sufficent)
* $background-special
*
* ## Bootstrap compatibility
*
* This mixin can be used to replace colors behaviors of Bootstrap buttons.
* Indeed, this mixin aims to replace each definition done by the
* 'button-variant' Bootstrap mixin.
*
*/
color: $color;
background-color: $background-base;
border-color: $border;
@include btn-special {
color: $color;
background-color: $background-special;
border-color: $border;
}
@include disabled {
@include hover-focus {
background-color: $background-base;
border-color: $border;
}
}
.badge {
color: $background-base;
background-color: $color;
}
}
.btn-primary {
@include button-variant-2modes($btn-font-color, $btn-bg-base, $btn-bg-special, $btn-border);
}
form#filter_form {
.form-group {
padding-right:20px;
}
ul.form-control {
background-color: transparent;
border: none;
box-shadow: none;
}
}

View file

@ -1,91 +0,0 @@
// main: global.scss
.strong-banner {
padding-top : 20px;
padding-bottom : 10px ;
background-color : $header-background;
color: $header-color;
}
.navbar-inverse {
background-color : $header-background;
background-color : transparent ;
border-style : none ;
.navbar-nav {
& > .open > a,
& > .open > a:focus,
& > .open > a:hover {
color: #fff;
background-color: $header-second-backgroud;
}
}
}
.navbar-collapse {
border-top: 0px solid transparent ;
padding : 0px ;
/* only < 768px*/
background-color : $header-second-backgroud;
padding-left: 25px;
margin-left: -15px;
margin-right: -15px;
@media (min-width: 768px) {
background-color : transparent;
padding-left: 0px;
margin-left: 0px;
margin-right: 0px;
}
}
.navbar-nav {
width: 100%;
@media (min-width: 768px) {
float : right ;
width: auto;
}
}
.navbar-inverse {
/* BRAND */
.navbar-brand {
&,
&:hover,
&:focus {
color: white;
font-family: $font_brand;
font-size: xx-large;
border-bottom: 5px solid $underline-brand;
}
}
/* ICONE */
.navbar-toggle {
&,
&:hover,
&:focus {
background-color: $second_bold_color;
border-color: $second_bold_color;
}
.icon-bar {
background-color: $second_white_color;
}
}
/* LINKS */
.navbar-nav {
& > li > a {
&,
&:hover,
&:focus {
font-family: $font_nav;
font-size: large;
color: $second_bold_color;
background: transparent;
@media (min-width: 768px) {
color: $second_white_color;
}
}
}
}
}

View file

@ -1,24 +0,0 @@
// main: global.scss
.message-info {
color : #31708f;
background-color: #d9edf7;
border-color : #bce8f1;
}
.message-success {
color : #3c763d;
background-color: #dff0d8;
border-color : #d6e9c6;
}
.message-warning {
color : #8a6d3b;
background-color: #fcf8e3;
border-color : #faebcc;
}
.message-error {
color : #a94442;
background-color: #f2dede;
border-color : #ebccd1;
}
.alert {
margin-bottom: 0;
}

View file

@ -1,38 +0,0 @@
// main: global.scss
.tree {
font-size:large;
ul, li {
position: relative;
}
ul {
list-style: none;
padding-left: 32px;
}
li::before,
li::after {
content: "";
position: absolute;
left: -12px;
}
li::before {
border-top: 3px solid $second_soft_color;
top: 9px;
width: 8px;
height: 0;
}
li::after {
border-left: 3px solid $second_soft_color;
height: 100%;
width: 0px;
top: 2px;
}
ul > li:last-child::after {
height: 8px;
}
.category_node {
}
}

View file

@ -1,99 +0,0 @@
// main: global.scss
$main_c7: #375362;
$main_c6: #4F778C;
$main_c5: #5D8CA6;
$main_c3: #BDD2DE;
$main_c1: #F0FAFF;
$neutral_c2 : #F2EDDC;
$neutral_c1: #FFFBEF;
$activity_c8: #4FADB8;
$activity_c7: #5ED1DC;
$activity_c3: #CAE4E7;
$event_c8: #3488A6;
$event_c7: #3999BA;
$event_c3: #AAD5E2;
$todo_c8: #F19F5D;
$todo_c7: #FF9C4D;
$todo_c3: #FFDEBC;
$equipment_c8: #E75571;
$equipment_c7: #FF5C79;
$equipment_c3: #FECAD6;
$staff_c8: #3BAD89;
$staff_c7: #42C2A2;
$staff_c3: #A9E1D7;
/* LEGACY COLORS*/
$main_bold_color: #FF6969;
$main_soft_color: #FF9191;
$main_white_color: #FFEBEB;
$second_bold_color: #FFB363;
$second_soft_color: #FFC282;
$second_white_color: #FFF5EB;
$third_bold_color: #48B0C7;
$third_soft_color: #8FD4E3;
$third_white_color: #DCEAED;
/* COLORS */
/* Header */
$header-background: $main_c7;
$header-second-backgroud: $main_c5;
$header-color: white;
$underline-brand: $equipment_c7;
/* Général */
$html-background: $neutral_c1;
$content-background: $neutral_c1;
$content-border: $main_c7;
$aside-background: $main_c7;
$aside-inverted-background: $main_c5;
$title_border: $main_c7;
/* Le reste */
$btn-font-color: white;
$btn-bg-base: $main_bold_color;
$btn-bg-special: $main_soft_color;
$btn-border: $main_bold_color;
$yes_color: #55C487;
$no_color: #E36268;
$dunno_color: #5599C4;
/* Titres */
$h1_background: $main_c6;
$h2_background: $main_c3;
$h3_background: $main_c1;
$h1_background_activity: $activity_c8;
$h2_background_activity: $activity_c7;
$h3_background_activity: $main_c1;
$h1_background_event: $event_c8;
$h2_background_event: $event_c7;
$h3_background_event: $main_c1;
$h1_background_todo: $todo_c8;
$h2_background_todo: $todo_c7;
$h3_background_todo: $main_c1;
$h1_background_equipment: $equipment_c8;
$h2_background_equipment: $equipment_c7;
$h3_background_equipment: $main_c1;
$h1_background_staff: $staff_c8;
$h2_background_staff: $staff_c7;
$h3_background_staff: $main_c1;
/* FONTS */
$font_brand:'Lily Script One', cursive;
$font_nav:'Work Sans', cursive;
$font_bold:'Capriola', sans-serif;
$font_normal:'Saira Semi Condensed', sans-serif;

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
global.min.css

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,416 +0,0 @@
/*NE PAS MODIFIER LE FICHIER .CSS, MAIS PLUTÔT LE
FICHIER .SCSS */
@import '_variables';
@import '_messages';
@import '_header';
@import '_forms';
@import '_tree';
@import '_filters';
//@import '_dashed-shadows';
/* MISE EN FORME GÉNÉRALE */
html {
height : 100% ;
background-color: $html-background;
}
body {
font-family: $font_normal;
font-size: medium;
}
#principal {
background-color: $html-background;
}
/*MAIN*/
main {
background-color:$content-background;
border-width: 2px;
border-color: $content-border;
border-style: none;
border-collapse: collapse;
display: table-cell;
margin-top:0px;
padding: 0px;
.fuid, h1, h2, h3 {
margin-left: -15px;
margin-right: -15px;
margin-top: 0px;
margin-bottom: 0px;
}
h1, h2, h3 {
margin-top: -2px;
margin-bottom: -2px;
margin-left: -17px;
margin-right: -17px;
color: white;
padding-left: 15px;
padding-right: 15px;
}
h1 {
font-family: $font_bold;
font-weight: 600;
padding-bottom: 10px;
padding-top: 15px;
background-color: $h1_background;
&.activity {
background-color: $h1_background_activity;
}
&.event {
background-color: $h1_background_event;
}
&.todo {
background-color: $h1_background_todo;
}
&.equipment {
background-color: $h1_background_equipment;
}
&.staff {
background-color: $h1_background_staff;
}
}
h2, h3 {
border-bottom : 2px solid $main_c5;
padding-bottom : 5px ;
padding-top: 10px;
font-family: $font_bold;
font-weight: 600;
font-size: x-large;
background-color: $h2_background;
&.activity {
background-color: $h2_background_activity;
border-color: $activity_c8;
}
&.event {
background-color: $h2_background_event;
border-color: $event_c8;
}
&.todo {
background-color: $h2_background_todo;
border-color: $todo_c8;
}
&.equipment {
background-color: $h2_background_equipment;
border-color: $equipment_c8;
}
&.staff {
background-color: $h2_background_staff;
border-color: $staff_c8;
}
}
h3 {
background-color: $h3_background;
color: $main_c6;
&.activity {
background-color: $h3_background_activity;
color: $activity_c7;
}
&.event {
background-color: $h3_background_event;
color: $event_c7;
}
&.todo {
background-color: $h3_background_todo;
color: $todo_c7;
}
&.equipment {
background-color: $h3_background_equipment;
color: $equipment_c7;
}
&.staff {
background-color: $h3_background_staff;
color: $staff_c7;
}
}
a {
color: $main_bold_color;
&:hover,
&:active,
&:focus {
color : $main_bold_color;
}
}
.text {
padding: 15px;
}
}
/*ASIDE*/
aside {
background-color:$aside-background;
color: white;
margin-top: 0px;
padding: 0px!important;
a {
color: $main_soft_color;
&:hover,
&:active,
&:focus {
color : $main_soft_color;
}
}
code {
color: $main_c7;
background-color: $main_c3;
}
.heading {
padding: 8px 15px;
font-size: 32px;
line-height: 1.3;
text-align:center;
&.inverted {
background-color:$second_white_color;
color:black;
}
&.small {
font-size: 25px;
.sub {
font-size: 0.7em;
font-weight: normal;
}
}
.sub {
font-size: 0.7em;
font-weight: normal;
}
&.separator {
border-bottom-color: $main_soft_color;
border-bottom-style: solid;
}
}
.text {
padding: 15px;
&.inverted {
background-color:$second_white_color;
color:black;
}
}
}
@media (min-width: 768px) {
main {
margin-top:20px;
border-style: dashed;
}
aside {
margin-top:20px;
}
}
hr {
border-top : 1px solid $second_bold_color ;
}
span.vsep {
padding-left: 5px;
padding-right: 5px;
}
div.tag-list {
margin-top: 20px;
}
code {
font-size: small;
}
.module-list {
margin-top: 10px;
margin-bottom: 10px;
display: flex;
align-items: stretch;
flex-wrap: wrap;
}
a.module {
padding: 20px 40px;
margin: 5px;
border-bottom-style: solid;
font-size: large;
display: block;
border-bottom-color: $main_c7;
background-color: $main_c3;
color: $main_c7;
&.activity {
border-bottom-color: $activity_c8;
background-color: $activity_c3;
color: $activity_c8;
}
&.event {
border-bottom-color: $event_c8;
background-color: $event_c3;
color: $event_c8;
}
&.todo {
border-bottom-color: $todo_c8;
background-color: $todo_c3;
color: $todo_c8;
}
&.equipment {
border-bottom-color: $equipment_c8;
background-color: $equipment_c3;
color: $equipment_c8;
}
&.staff {
border-bottom-color: $staff_c8;
background-color: $staff_c3;
color: $staff_c8;
}
&:hover,
&:active,
&:focus {
text-decoration: none;
color: $main_c1;
background-color: $main_c7;
&.activity {
background-color: $activity_c7;
}
&.event {
background-color: $event_c7;
}
&.equipment {
background-color: $equipment_c7;
}
&.todo {
background-color: $todo_c7;
}
&.staff {
background-color: $staff_c7;
}
}
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
&:after {
content: '\002B';
color: white;
font-weight: bold;
float: right;
margin-left: 5px;
}
&:hover {
background-color: #555;
}
h3 {
margin-top: 0px;
margin-bottom: 0px;
display: inline-block;
}
}
.active {
background-color: #555;
&:after {
content: "\2212";
}
}
.content {
padding: 8px 18px;
display : none;
background-color: #f1f1f1;
&.fluid {
padding: 0px 0px;
background-color: transparent;
}
h4 {
font-size: x-large;
font-weight: bold;
display: inline-block;
margin: 0px 0px;
}
}
.planning {
.activity {
padding: 8px 18px;
padding-top: 8px;
padding-bottom: 12px;
border-left: none;
border-right: 6px solid $main_soft_color;
&.inverted {
border-left: 6px solid $main_soft_color;
border-right: none;
}
.activity-title {
font-size: large;
}
}
}
.glyphicon {
&.yes {
color:$yes_color!important;
}
&.no {
color:$no_color!important;
}
&.dunno {
color:$dunno_color!important;
}
}
.sending-request {
position: relative;
&:after {
content: "Chargement...";
position: absolute;
width: 100%;
height: 100%;
background: #fff;
opacity: 0.8;
color: #777;
text-align: center;
box-sizing: border-box;
z-index: 5;
top: 0;
left: 0;
padding: 8%;
}
}

View file

@ -1,71 +0,0 @@
/*
Error: Undefined variable: "$main_bold_color".
on line 6 of header.scss
1: /* BANNER *\/
2:
3: .strong-banner {
4: padding-top : 20px;
5: padding-bottom : 10px ;
6: background-color : $main_bold_color;
7: color: $second_bold_color;
8: }
9:
10:
11: .navbar-inverse {
Backtrace:
header.scss:6
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/script/tree/variable.rb:49:in `_perform'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/script/tree/node.rb:50:in `perform'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:402:in `visit_prop'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `block in visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `block in with_base'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:135:in `with_frame'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `with_base'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `block (2 levels) in visit_rule'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `map'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `block in visit_rule'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:183:in `with_environment'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:442:in `visit_rule'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `block in visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `block in with_base'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:135:in `with_frame'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `with_base'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `block in visit_children'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `map'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `visit_children'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:171:in `block in visit_children'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:183:in `with_environment'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:170:in `visit_children'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `block in visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:190:in `visit_root'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:161:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:10:in `visit'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/root_node.rb:36:in `css_tree'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/root_node.rb:29:in `render_with_sourcemap'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/engine.rb:389:in `_render_with_sourcemap'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/engine.rb:307:in `render_with_sourcemap'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:462:in `update_stylesheet'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:215:in `block in update_stylesheets'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:209:in `each'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:209:in `update_stylesheets'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:294:in `watch'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin.rb:109:in `method_missing'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/sass_scss.rb:360:in `watch_or_update'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/sass_scss.rb:51:in `process_result'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/base.rb:52:in `parse'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/base.rb:19:in `parse!'
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/bin/sass:13:in `<top (required)>'
/usr/bin/sass:23:in `load'
/usr/bin/sass:23:in `<main>'
*/
body:before {
white-space: pre;
font-family: monospace;
content: "Error: Undefined variable: \"$main_bold_color\".\A on line 6 of header.scss\A \A 1: /* BANNER */\A 2: \A 3: .strong-banner {\A 4: padding-top : 20px;\A 5: padding-bottom : 10px ;\A 6: background-color : $main_bold_color;\A 7: color: $second_bold_color;\A 8: }\A 9: \A 10: \A 11: .navbar-inverse {"; }

View file

@ -0,0 +1,126 @@
@media (max-width: 768px) {
.header {
position: absolute;
}
/*sidebar*/
#sidebar {
height: auto;
overflow: hidden;
position: absolute;
width: 100%;
z-index: 1001;
}
/* body container */
#main-content {
margin: 0px!important;
position: none !important;
}
#sidebar > ul > li > a > span {
line-height: 35px;
}
#sidebar > ul > li {
margin: 0 10px 5px 10px;
}
#sidebar > ul > li > a {
height:35px;
line-height:35px;
padding: 0 10px;
text-align: left;
}
#sidebar > ul > li > a i{
/*display: none !important;*/
}
#sidebar ul > li > a .arrow, #sidebar > ul > li > a .arrow.open {
margin-right: 10px;
margin-top: 15px;
}
#sidebar ul > li.active > a .arrow, #sidebar ul > li > a:hover .arrow, #sidebar ul > li > a:focus .arrow,
#sidebar > ul > li.active > a .arrow.open, #sidebar > ul > li > a:hover .arrow.open, #sidebar > ul > li > a:focus .arrow.open{
margin-top: 15px;
}
#sidebar > ul > li > a, #sidebar > ul > li > ul.sub > li {
width: 100%;
}
#sidebar > ul > li > ul.sub > li > a {
background: transparent !important ;
}
#sidebar > ul > li > ul.sub > li > a:hover {
}
/* sidebar */
#sidebar {
margin: 0px !important;
}
/* sidebar collabler */
#sidebar .btn-navbar.collapsed .arrow {
display: none;
}
#sidebar .btn-navbar .arrow {
position: absolute;
right: 35px;
width: 0;
height: 0;
top:48px;
border-bottom: 15px solid #282e36;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
/*---------*/
.btn {
margin-bottom: 5px;
}
ul.sidebar-menu li ul.sub li a {
padding: 0;
}
/*---*/
.img-responsive {
width: 100%;
}
}
@media (max-width: 480px) {
#top_menu .nav > li, ul.top-menu > li {
float: right;
}
.hidden-phone {
display: none !important;
}
}
@media (max-width:320px) {
#top_menu .nav > li, ul.top-menu > li {
float: right;
}
.hidden-phone {
display: none !important;
}
}

Some files were not shown because too many files have changed in this diff Show more