Compare commits
3 commits
master
...
mdebray/re
Author | SHA1 | Date | |
---|---|---|---|
|
84af71a6c7 | ||
|
f8b96586db | ||
|
b49accf96e |
50 changed files with 162 additions and 999 deletions
|
@ -1 +0,0 @@
|
|||
insecure-secret_key
|
|
@ -1,28 +1,40 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from loadcredential import Credentials
|
||||
|
||||
credentials = Credentials(env_prefix="ERNESTOPHONE_")
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
DEBUG = credentials.get_json("DEBUG", False)
|
||||
|
||||
ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
|
||||
|
||||
SECRET_KEY = credentials["SECRET_KEY"]
|
||||
|
||||
ADMINS = credentials.get_json("ADMINS", [])
|
||||
|
||||
SERVER_EMAIL = credentials.get("SERVER_EMAIL", "ernesto@localhost")
|
||||
try:
|
||||
from . import secret
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"The secret.py file is missing.\n"
|
||||
"For a development environment, simply copy secret_example.py"
|
||||
)
|
||||
|
||||
|
||||
ACCOUNT_CREATION_PASS = credentials.get("ACCOUNT_CREATION_PASS", "dummy")
|
||||
def import_secret(name):
|
||||
"""
|
||||
Shorthand for importing a value from the secret module and raising an
|
||||
informative exception if a secret is missing.
|
||||
"""
|
||||
try:
|
||||
return getattr(secret, name)
|
||||
except AttributeError:
|
||||
raise RuntimeError("Secret missing: {}".format(name))
|
||||
|
||||
|
||||
SECRET_KEY = import_secret("SECRET_KEY")
|
||||
ADMINS = import_secret("ADMINS")
|
||||
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
||||
|
||||
DBNAME = import_secret("DBNAME")
|
||||
DBUSER = import_secret("DBUSER")
|
||||
DBPASSWD = import_secret("DBPASSWD")
|
||||
|
||||
ACCOUNT_CREATION_PASS = import_secret("ACCOUNT_CREATION_PASS")
|
||||
|
||||
BASE_DIR = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"propositions",
|
||||
"trombonoscope",
|
||||
"actu",
|
||||
"colorful",
|
||||
|
@ -77,22 +89,15 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = "Ernestophone.wsgi.application"
|
||||
|
||||
|
||||
###
|
||||
# Database configuration
|
||||
# -> https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
|
||||
DATABASES = credentials.get_json(
|
||||
"DATABASES",
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
},
|
||||
)
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": DBNAME,
|
||||
"USER": DBUSER,
|
||||
"PASSWORD": DBPASSWD,
|
||||
"HOST": "localhost",
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
|
@ -123,7 +128,7 @@ USE_L10N = True
|
|||
USE_TZ = True
|
||||
|
||||
|
||||
LOCALE_PATHS = (BASE_DIR / "locale",)
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
|
||||
|
||||
|
||||
AUTH_PROFILE_MODEL = "gestion.ErnestoUser"
|
||||
|
@ -144,18 +149,3 @@ AVATAR_PROVIDERS = (
|
|||
"avatar.providers.DefaultAvatarProvider",
|
||||
)
|
||||
AVATAR_THUMB_FORMAT = "JPEG"
|
||||
|
||||
###
|
||||
# Staticfiles configuration
|
||||
|
||||
STATIC_ROOT = credentials["STATIC_ROOT"]
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
MEDIA_ROOT = credentials.get("MEDIA_ROOT", BASE_DIR / "media")
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
if DEBUG:
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
INSTALLED_APPS += ["debug_toolbar"]
|
||||
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
23
Ernestophone/settings/local.py
Normal file
23
Ernestophone/settings/local.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import os
|
||||
|
||||
from .common import * # noqa
|
||||
from .common import BASE_DIR, INSTALLED_APPS, MIDDLEWARE
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
DEBUG = True
|
||||
|
||||
INSTALLED_APPS += ["debug_toolbar"]
|
||||
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "static"
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = "media"
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||
}
|
||||
}
|
17
Ernestophone/settings/prod.py
Normal file
17
Ernestophone/settings/prod.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
|
||||
from .common import * # noqa
|
||||
from .common import BASE_DIR
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"ernestophone.ens.fr",
|
||||
"www.ernestophone.ens.fr",
|
||||
]
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "..", "..", "public", "ernesto", "static")
|
||||
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "..", "media")
|
9
Ernestophone/settings/secret_example.py
Normal file
9
Ernestophone/settings/secret_example.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
SECRET_KEY = "vpb%-1@$ha98w5^ji#@9v2_kxj)zdk5+e!9!fqniu2$#eg+46="
|
||||
ADMINS = None
|
||||
SERVER_EMAIL = "ernesto@localhost"
|
||||
|
||||
DBNAME = "ernesto"
|
||||
DBUSER = "ernesto"
|
||||
DBPASSWD = "YNp2rrXowJnDAFF3"
|
||||
|
||||
ACCOUNT_CREATION_PASS = "dummy"
|
|
@ -13,13 +13,13 @@ Including another URLconf
|
|||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import include, path
|
||||
|
||||
from gestion import views as gestion_views
|
||||
|
||||
urlpatterns = []
|
||||
|
@ -69,5 +69,4 @@ urlpatterns += i18n_patterns(
|
|||
),
|
||||
prefix_default_language=False,
|
||||
)
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
@ -3,26 +3,14 @@ from django.contrib import admin
|
|||
from .models import Event, Participants
|
||||
|
||||
|
||||
class ParticipantsAdmin(admin.ModelAdmin):
|
||||
fields = [
|
||||
"event",
|
||||
"participant",
|
||||
"reponse",
|
||||
"instrument",
|
||||
"instrument_autre",
|
||||
"dont_play_main",
|
||||
"details",
|
||||
"creationDate",
|
||||
"updateDate",
|
||||
]
|
||||
readonly_fields = ["creationDate", "updateDate"]
|
||||
list_display = ["participant", "event", "reponse", "creationDate", "updateDate"]
|
||||
def has_add_permission(self, req):
|
||||
return False
|
||||
def has_change_permission(self,obj, change=False):
|
||||
return False
|
||||
|
||||
|
||||
# Add event by admin page return a 502 error
|
||||
admin.site.register(Event)
|
||||
class ParticipantsAdmin(admin.ModelAdmin):
|
||||
autocomplete_fields = ["event", "participant"]
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
search_fields = ["nom", "nomcourt", "lieu", "description", "desc_users"]
|
||||
|
||||
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(Participants, ParticipantsAdmin)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from gestion.models import ErnestoUser
|
||||
|
||||
from calendrier.models import Event, Participants
|
||||
from gestion.models import ErnestoUser
|
||||
|
||||
|
||||
class ModifEventForm(forms.ModelForm):
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-03-14 23:20
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("calendrier", "0006_auto_20210929_1629"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="participants",
|
||||
name="creationDate",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="Date de création",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="participants",
|
||||
name="updateDate",
|
||||
field=models.DateTimeField(
|
||||
auto_now=True, verbose_name="Dernière mise à jour"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 2.2.27 on 2022-03-22 13:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('calendrier', '0007_auto_20220314_2320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddConstraint(
|
||||
model_name='participants',
|
||||
constraint=models.UniqueConstraint(fields=('event', 'participant'), name='reponse unique aux event'),
|
||||
),
|
||||
]
|
|
@ -2,6 +2,7 @@ import uuid
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from gestion.models import INSTRU_CHOICES, ErnestoUser
|
||||
|
||||
ANSWERS = (
|
||||
|
@ -72,11 +73,3 @@ class Participants(models.Model):
|
|||
choices=[("Non", _("Non")), ("Oui", _("Oui"))],
|
||||
)
|
||||
details = models.CharField(max_length=50, blank=True)
|
||||
creationDate = models.DateTimeField(
|
||||
auto_now_add=True, verbose_name=_("Date de création")
|
||||
)
|
||||
updateDate = models.DateTimeField(
|
||||
auto_now=True, verbose_name=_("Dernière mise à jour")
|
||||
)
|
||||
class Meta:
|
||||
constraints = [ models.UniqueConstraint(fields=['event', 'participant'], name='reponse unique aux event') ]
|
||||
|
|
|
@ -46,15 +46,7 @@
|
|||
<a href="mailto:fanfare@ens.fr" class="icon fa-envelope fa-5x "><span
|
||||
|
||||
class="label">Mail</span></a>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UC_RahrdZLoBGpJQHwegV4tQ" class="icon fa-youtube-play fa-5x"><span class="label">Youtube</span> </a>
|
||||
|
||||
<a target="_blank" href="https://www.instagram.com/ernestophone/" class="icon fa-instagram fa-5x"><span class="label">Instagram</span></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "gestion/base.html" %}
|
||||
{% load static %}
|
||||
{% load staticfiles %}
|
||||
{% load frenchmonth %}
|
||||
{% load i18n %}
|
||||
{% load translate %}
|
||||
|
@ -43,35 +43,35 @@
|
|||
<tr>
|
||||
<td width="20%" align="left">
|
||||
<<
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
<a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName|frenchmonth}} {{PreviousYear}}</a>
|
||||
{% else %}
|
||||
<a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName}} {{PreviousYear}}</a>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
</td>
|
||||
<td width="20%" align="center"><a href="{% url "calendrier:home" %}">{% trans "Aujourd'hui" %}</a></td>
|
||||
<td width="20%" align="right">
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
<a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName|frenchmonth}} {{NextYear}}</a> >>
|
||||
{% else %}
|
||||
<a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName}} {{NextYear}}</a> >>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="calendar">
|
||||
{% if user.profile.is_chef_event or user.profile.is_chef %}
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
{{Calendar_chef|translate}}
|
||||
{% else %}
|
||||
{{Calendar_chef}}
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% else %}
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
{{Calendar|translate}}
|
||||
{% else %}
|
||||
{{Calendar}}
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% endif%}
|
||||
</div>
|
||||
{% if user.profile.is_chef_event or user.profile.is_chef %}
|
||||
|
|
|
@ -223,9 +223,6 @@
|
|||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% if event.id == 573 %}
|
||||
<div class="fireworks" style="pointer-events: none; position:fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; weight: 100%;"></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script>
|
||||
|
@ -261,19 +258,11 @@ singleEvent.createWidget('#single-normal', function() {
|
|||
console.log('#single-normal widget has been created');
|
||||
});
|
||||
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
singleEvent.setOption({ lang: 'fr' });
|
||||
{% else %}
|
||||
singleEvent.setOption({ lang: 'en' });
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
</script>
|
||||
{% if event.id == 573 %}
|
||||
<script src="https://unpkg.com/fireworks-js@2.x/dist/index.umd.js"></script>
|
||||
<script>
|
||||
const container = document.querySelector('.fireworks');
|
||||
const fireworks = new Fireworks.default(container);
|
||||
fireworks.updateOptions({ acceleration: 1.01, traceSpeed: 5 });
|
||||
fireworks.start();
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model
|
|||
from django.template.defaultfilters import urlencode
|
||||
from django.test import Client, TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from gestion.models import ErnestoUser
|
||||
|
||||
from ..models import Event
|
||||
|
|
|
@ -4,20 +4,20 @@ from calendar import monthrange
|
|||
from collections import defaultdict
|
||||
from datetime import date, datetime
|
||||
|
||||
from actu.models import Actu
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic import DeleteView, TemplateView, UpdateView
|
||||
from gestion.mixins import ChefEventRequiredMixin
|
||||
from gestion.models import Photo
|
||||
|
||||
from actu.models import Actu
|
||||
from calendrier.calend import EventCalendar
|
||||
from calendrier.forms import (ChangeDoodleName, EventForm, ModifEventForm,
|
||||
ParticipantsForm)
|
||||
from calendrier.models import Event, Participants
|
||||
from gestion.mixins import ChefEventRequiredMixin, ChefRequiredMixin
|
||||
from gestion.models import Photo
|
||||
|
||||
|
||||
def generer(*args):
|
||||
|
@ -212,7 +212,6 @@ class ViewEvent(LoginRequiredMixin, TemplateView):
|
|||
else:
|
||||
instru = participant.instrument
|
||||
|
||||
instru = "" if instru is None else instru
|
||||
sure, maybe, namesoui, namespe, namesnon = instrument_count[instru]
|
||||
|
||||
if participant.reponse == "oui":
|
||||
|
@ -287,7 +286,7 @@ class ViewEvent(LoginRequiredMixin, TemplateView):
|
|||
|
||||
context["event"] = event
|
||||
context["instrument_count"] = instrument_count_l
|
||||
context["participants"] = participants.order_by('-creationDate')
|
||||
context["participants"] = participants
|
||||
context["nboui"] = len(participants.filter(reponse="oui"))
|
||||
context["nbpe"] = len(participants.filter(reponse="pe"))
|
||||
context["nbnon"] = len(participants.filter(reponse="non"))
|
||||
|
@ -374,16 +373,16 @@ class ReponseEvent(LoginRequiredMixin, TemplateView):
|
|||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.form_class(request.POST)
|
||||
ev = get_object_or_404(Event, id=self.kwargs["id"])
|
||||
part = request.user.profile
|
||||
try:
|
||||
p = Participants.objects.get(event=ev, participant=part)
|
||||
except Participants.DoesNotExist:
|
||||
p = None
|
||||
form = self.form_class(request.POST, instance=p)
|
||||
if form.is_valid():
|
||||
try:
|
||||
p = Participants.objects.get(event=ev, participant=part)
|
||||
p.delete()
|
||||
except Participants.DoesNotExist:
|
||||
pass
|
||||
obj = form.save(commit=False)
|
||||
# Si la participation existe déjà, ces 2 ligne sont redondantes
|
||||
obj.event = ev
|
||||
obj.participant = part
|
||||
obj.save()
|
||||
|
|
|
@ -114,8 +114,19 @@ class UserProfileAdmin(UserAdmin):
|
|||
user.save()
|
||||
|
||||
|
||||
class ErnestoUserAdmin(admin.ModelAdmin):
|
||||
search_fields = [
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"phone",
|
||||
"instru",
|
||||
]
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserProfileAdmin)
|
||||
admin.site.register(ErnestoUser, ErnestoUserAdmin)
|
||||
admin.site.register(VideoGallery)
|
||||
admin.site.register(Photo)
|
||||
admin.site.register(Actu)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 46 KiB |
|
@ -124,11 +124,11 @@
|
|||
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
{% if current_language == "fr" %}
|
||||
{% ifequal current_language "fr" %}
|
||||
<a class="nav-link" href="{% changelang "en" %}" ><img src="{% static 'images\en_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a>
|
||||
{% else %}
|
||||
<a class="nav-link" href="{% changelang "fr" %}" ><img src="{% static 'images\fr_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -145,10 +145,6 @@
|
|||
<!-- Footer -->
|
||||
<footer id="footer" style="background-color:rgb(228, 82, 47);">
|
||||
<div class="copyright">
|
||||
<ul class="icons">
|
||||
<li><a target="_blank" href="https://cvec.etudiant.gouv.fr/"><img alt="Logo de la CVEC" src='{% static "images/cvec.png" %}' width="100px"/></a></li>
|
||||
|
||||
</ul>
|
||||
<ul class="icons">
|
||||
<li><a target="_blank" href="https://www.facebook.com/ernestophone"
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<div class="7u 12u$(small)">
|
||||
<p>{% trans "Propriétaire : "%} {% if instru.owner %}{{instru.owner}} {% else %}-{% endif %}<br>
|
||||
{% trans "Statut : "%} {{instru.statut}} <br>
|
||||
{% if instru.statut == 'Prêté' %}
|
||||
{% ifequal instru.statut 'Prêté' %}
|
||||
{% trans "Utilisateur : "%} {% if instru.user %}{{instru.user}} {% else %}-{% endif %}<br>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% trans "Marque : "%} {% if instru.marque %}{{instru.marque}} {% else %}-{% endif %} <br>
|
||||
{% trans "Modele : "%} {% if instru.model %}{{instru.model}} {% else %}-{% endif %}<br>
|
||||
{% trans "Numéro de série : "%} {% if instru.serial %}{{instru.serial}}{% else %}-{% endif %} <br>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<tr>
|
||||
|
||||
<td> {{ rep.date }} </td>
|
||||
<td> {% if current_language == "fr" %}
|
||||
<td> {% ifequal current_language "fr" %}
|
||||
{{ rep.description }}
|
||||
{% else %}
|
||||
{% if instru.description_en %}
|
||||
|
@ -63,7 +63,7 @@
|
|||
{% else %}
|
||||
{{ rep.description }}
|
||||
{% endif %}
|
||||
{% endif %} </td>
|
||||
{% endifequal %} </td>
|
||||
<td> {{ rep.prix }} </td>
|
||||
<td> {{ rep.lieux }} </td>
|
||||
{%if user.profile.is_chef or user.profile.is_chef_instru %}
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Ernestophone.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Ernestophone.settings.local")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
data = builtins.fromJSON (builtins.readFile ./sources.json);
|
||||
version = data.version;
|
||||
|
||||
mkSource =
|
||||
spec:
|
||||
assert spec ? type;
|
||||
let
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
hash,
|
||||
branch ? null,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null then
|
||||
(builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash; # FIXME: check nix version & use SRI hashes
|
||||
})
|
||||
else
|
||||
assert repository.type == "Git";
|
||||
let
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName repository.url revision;
|
||||
in
|
||||
builtins.fetchGit {
|
||||
url = repository.url;
|
||||
rev = revision;
|
||||
inherit name;
|
||||
# hash = hash;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
in
|
||||
if version == 3 then
|
||||
builtins.mapAttrs (_: mkSource) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"pins": {
|
||||
"nix-pkgs": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "Git",
|
||||
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs.git"
|
||||
},
|
||||
"branch": "main",
|
||||
"revision": "cc01e1c2a6ecb1e38fde35ee54995a6a639fb057",
|
||||
"url": null,
|
||||
"hash": "17a9vlwrk9365ccyl7a5xspqsn9wizcpwdpvr3qdimvq4fpwhjal"
|
||||
},
|
||||
"nixpkgs": {
|
||||
"type": "Channel",
|
||||
"name": "nixpkgs-unstable",
|
||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre723402.4989a246d7a3/nixexprs.tar.xz",
|
||||
"hash": "0hjng6rhkjiql1dqbanjm6jl6npik29q2lmba032j897fhyzin91"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestion', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Prop',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||
('nom', models.CharField(max_length=100)),
|
||||
('artiste', models.CharField(max_length=100, blank=True)),
|
||||
('lien', models.URLField(blank=True)),
|
||||
('nboui', models.IntegerField(verbose_name='oui', default=0)),
|
||||
('nbnon', models.IntegerField(verbose_name='non', default=0)),
|
||||
('user', models.ForeignKey(verbose_name='Proposé par', to='gestion.ErnestoUser', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Proposition',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Reponses',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||
('reponse', models.CharField(verbose_name='Réponse', choices=[('oui', 'Oui'), ('non', 'Non')], max_length=20, blank=True)),
|
||||
('part', models.ForeignKey(to='gestion.ErnestoUser', on_delete=models.CASCADE)),
|
||||
('prop', models.ForeignKey(to='propositions.Prop', on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 2.2.9 on 2020-01-04 23:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('propositions', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='prop',
|
||||
name='nom',
|
||||
field=models.CharField(max_length=100, verbose_name='nom du morceau'),
|
||||
),
|
||||
]
|
|
@ -1,75 +0,0 @@
|
|||
# Generated by Django 2.2.9 on 2020-01-05 13:32
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def move_profile_to_user(apps, schema_editor):
|
||||
Reponses = apps.get_model("propositions", "reponses")
|
||||
for answer in Reponses.objects.all():
|
||||
answer.user = answer.part.user
|
||||
answer.save()
|
||||
|
||||
|
||||
def move_user_to_profile(apps, schema_editor):
|
||||
# One should do something similar to ``move_profile_to_user`` AND make the
|
||||
# ``part`` field temporarily nullable in the operations below.
|
||||
# => Grosse flemme
|
||||
raise NotImplementedError("Who uses migrations backwards anyway?")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("gestion", "0001_initial"),
|
||||
("propositions", "0002_nom_verbose_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="reponses",
|
||||
options={
|
||||
"verbose_name": "Réponse à une proposition",
|
||||
"verbose_name_plural": "Réponses à une proposition",
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="reponses", old_name="prop", new_name="proposition",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="reponses",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(move_profile_to_user, move_user_to_profile),
|
||||
migrations.AlterField(
|
||||
model_name="reponses",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
null=False,
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(model_name="reponses", name="part"),
|
||||
migrations.AddField(
|
||||
model_name="reponses",
|
||||
name="answer",
|
||||
field=models.CharField(
|
||||
choices=[("oui", "Oui"), ("non", "Non")],
|
||||
default="non",
|
||||
max_length=3,
|
||||
verbose_name="Réponse",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="reponses", unique_together={("proposition", "user")},
|
||||
),
|
||||
migrations.RemoveField(model_name="reponses", name="reponse",),
|
||||
migrations.RenameModel(old_name="reponses", new_name="answer"),
|
||||
]
|
|
@ -1,43 +0,0 @@
|
|||
# Generated by Django 2.2.9 on 2020-01-05 14:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("propositions", "0003_reponse_renaming_and_cleaning"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="prop",
|
||||
options={
|
||||
"verbose_name": "Proposition de morceau",
|
||||
"verbose_name_plural": "Propositions de morceaux",
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="prop", old_name="artiste", new_name="artist",
|
||||
),
|
||||
migrations.RenameField(model_name="prop", old_name="lien", new_name="link"),
|
||||
migrations.RenameField(model_name="prop", old_name="nom", new_name="name"),
|
||||
migrations.RenameField(model_name="prop", old_name="nbnon", new_name="nb_no"),
|
||||
migrations.RenameField(model_name="prop", old_name="nboui", new_name="nb_yes"),
|
||||
migrations.AlterField(
|
||||
model_name="prop",
|
||||
name="nb_no",
|
||||
field=models.IntegerField(default=0, verbose_name="nombre de réponses non"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="prop",
|
||||
name="nb_yes",
|
||||
field=models.IntegerField(default=0, verbose_name="nombre de réponses oui"),
|
||||
),
|
||||
migrations.RenameModel(old_name="prop", new_name="proposition"),
|
||||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='proposition',
|
||||
field=models.ForeignKey(on_delete=models.deletion.CASCADE, to='propositions.Proposition'),
|
||||
),
|
||||
]
|
|
@ -1,24 +0,0 @@
|
|||
# Generated by Django 2.2.9 on 2020-01-05 15:26
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("propositions", "0004_prop_renaming_and_cleaning"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(model_name="proposition", name="nb_no",),
|
||||
migrations.RemoveField(model_name="proposition", name="nb_yes",),
|
||||
migrations.AlterField(
|
||||
model_name="answer",
|
||||
name="proposition",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="propositions.Proposition",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,53 +0,0 @@
|
|||
# Generated by Django 2.2.9 on 2020-01-05 16:28
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def move_profile_to_user(apps, schema_editor):
|
||||
Proposition = apps.get_model("propositions", "Proposition")
|
||||
for proposition in Proposition.objects.all():
|
||||
proposition.user = proposition.profile.user
|
||||
proposition.save()
|
||||
|
||||
|
||||
def move_user_to_profile(apps, schema_editor):
|
||||
# One should do something similar to ``move_profile_to_user`` AND make the
|
||||
# ``profile`` field temporarily nullable in the operations below.
|
||||
# => Grosse flemme
|
||||
raise NotImplementedError("Who uses migrations backwards anyway?")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("propositions", "0005_remove_nb_yes_no_fields"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="proposition", old_name="user", new_name="profile"
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="proposition",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Proposé par",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(move_profile_to_user, move_user_to_profile),
|
||||
migrations.RemoveField(model_name="proposition", name="profile"),
|
||||
migrations.AlterField(
|
||||
model_name="proposition",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Proposé par",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 2.2.27 on 2022-03-22 13:55
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('propositions', '0006_proposition_profile_to_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='proposition',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 2.2.27 on 2022-09-14 10:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('propositions', '0007_auto_20220322_1455'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='proposition',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 2.2.28 on 2024-06-15 13:03
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('propositions', '0008_auto_20220914_1244'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='proposition',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
|
||||
),
|
||||
]
|
|
@ -1,34 +0,0 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Proposition(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="nom du morceau")
|
||||
artist = models.CharField(blank=True, max_length=100)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Proposé par")
|
||||
link = models.URLField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Proposition de morceau"
|
||||
verbose_name_plural = "Propositions de morceaux"
|
||||
|
||||
|
||||
class Answer(models.Model):
|
||||
YES = "oui"
|
||||
NO = "non"
|
||||
|
||||
REP_CHOICES = [(YES, "Oui"), (NO, "Non")]
|
||||
|
||||
proposition = models.ForeignKey(Proposition, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
answer = models.CharField("Réponse", max_length=3, choices=REP_CHOICES)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("proposition", "user")
|
||||
verbose_name = "Réponse à une proposition"
|
||||
verbose_name_plural = "Réponses à une proposition"
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "gestion/base.html" %}
|
||||
|
||||
{% block titre %}Proposition de morceau{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p><a href="{% url "propositions:list" %}">Retour aux propositions</a></p>
|
||||
<form action="{% url "propositions:create" %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Enregistrer" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "gestion/base.html" %}
|
||||
|
||||
{% block titre %}Suppression d'une proposition{% endblock %}
|
||||
{% block content %}<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p><a href="{% url "propositions:list" %}">Retour aux propositions</a></p>
|
||||
<p>Voulez vous vraiment supprimer la proposition {{ object }}?</p>
|
||||
<input type="submit" value="Oui" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,44 +0,0 @@
|
|||
{% extends "gestion/base.html" %}
|
||||
|
||||
{% block titre %}Propositions de morceau{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Liste des propositions</h1>
|
||||
|
||||
<p><a href="{% url "propositions:create" %}">Proposer un morceau</a></p>
|
||||
|
||||
{% if propositions.exists %}
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Oui</th>
|
||||
<th>Non</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for p in propositions %}
|
||||
<tr class="prop">
|
||||
<td>
|
||||
{% if p.link %}<a href={{ p.link }}>{% endif %}
|
||||
<b>{{ p.name }}</b>{% if p.artist %} - {{ p.artist }}{% endif %}
|
||||
{% if p.link %}</a>{% endif %}
|
||||
</td>
|
||||
<td>{{ p.nb_yes }}</td>
|
||||
<td>{{ p.nb_no }}</td>
|
||||
<td><a href="{% url "propositions:oui" p.id %}">Oui</a></td>
|
||||
<td><a href="{% url "propositions:non" p.id %}">Non</a></td>
|
||||
<td>{% if p.user_answer %}Vous avez voté {{ p.user_answer }}{% endif %}</td>
|
||||
<td>
|
||||
{% if p.user == request.user or request.user.profile.is_chef %}
|
||||
<a class="supprimer" href="{% url "propositions:delete" p.id %}">Supprimer</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
Pas de proposition pour le moment
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,165 +0,0 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from gestion.models import ErnestoUser
|
||||
from propositions.models import Answer, Proposition
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def new_user(username):
|
||||
u = User.objects.create_user(username=username)
|
||||
ErnestoUser.objects.create(user=u, slug=username, is_ernesto=True)
|
||||
return u
|
||||
|
||||
|
||||
class PropositionCreateTest(TestCase):
|
||||
url = reverse_lazy("propositions:create")
|
||||
|
||||
def test_anonymous_cannot_get(self):
|
||||
response = Client().get(self.url)
|
||||
self.assertRedirects(response, "/login?next={}".format(self.url))
|
||||
|
||||
def test_ernesto_user_can_get(self):
|
||||
user = new_user("toto")
|
||||
client = Client()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_ernesto_user_can_post(self):
|
||||
user = new_user("toto")
|
||||
client = Client()
|
||||
client.force_login(user)
|
||||
|
||||
data = {"name": "foo", "artist": "bar", "link": "example.com"}
|
||||
client.post(self.url, data)
|
||||
proposition = Proposition.objects.all()
|
||||
self.assertEqual(1, proposition.count())
|
||||
|
||||
proposition = proposition.get()
|
||||
self.assertEqual(proposition.name, "foo")
|
||||
self.assertEqual(proposition.artist, "bar")
|
||||
self.assertEqual(proposition.link, "http://example.com")
|
||||
self.assertEqual(proposition.user, user)
|
||||
|
||||
|
||||
class PropositionDeleteTest(TestCase):
|
||||
def setUp(self):
|
||||
self.owner = new_user("owner")
|
||||
self.random_user = new_user("toto")
|
||||
self.chef = new_user("chef")
|
||||
self.chef.profile.is_chef = True
|
||||
self.chef.profile.save()
|
||||
|
||||
proposition = Proposition.objects.create(name="prop", user=self.owner)
|
||||
self.url = reverse("propositions:delete", args=(proposition.id,))
|
||||
|
||||
def test_anonymous_cannot_get(self):
|
||||
response = Client().get(self.url)
|
||||
self.assertRedirects(response, "/login?next={}".format(self.url))
|
||||
|
||||
def test_anonymous_cannot_post(self):
|
||||
response = Client().post(self.url, {})
|
||||
self.assertRedirects(response, "/login?next={}".format(self.url))
|
||||
self.assertTrue(Proposition.objects.exists())
|
||||
|
||||
def test_random_user_cannot_get(self):
|
||||
client = Client()
|
||||
client.force_login(self.random_user)
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_not_owner_cannot_post(self):
|
||||
client = Client()
|
||||
client.force_login(self.random_user)
|
||||
response = client.post(self.url, {})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertTrue(Proposition.objects.exists())
|
||||
|
||||
def test_chef_can_get(self):
|
||||
client = Client()
|
||||
client.force_login(self.chef)
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_chef_can_post(self):
|
||||
client = Client()
|
||||
client.force_login(self.chef)
|
||||
client.post(self.url, {})
|
||||
self.assertFalse(Proposition.objects.exists())
|
||||
|
||||
def test_owner_can_get(self):
|
||||
client = Client()
|
||||
client.force_login(self.owner)
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_owner_can_post(self):
|
||||
client = Client()
|
||||
client.force_login(self.owner)
|
||||
client.post(self.url, {})
|
||||
self.assertFalse(Proposition.objects.exists())
|
||||
|
||||
|
||||
class PropositionListTest(TestCase):
|
||||
url = reverse_lazy("propositions:list")
|
||||
|
||||
def setUp(self):
|
||||
self.user = new_user("toto")
|
||||
for name in ["foo", "bar", "baz"]:
|
||||
p = Proposition.objects.create(name=name, user=self.user)
|
||||
Answer.objects.create(proposition=p, user=self.user, answer=Answer.YES)
|
||||
for name in ["oof", "rab", "zab"]:
|
||||
p = Proposition.objects.create(name=name, user=self.user)
|
||||
Answer.objects.create(proposition=p, user=self.user, answer=Answer.NO)
|
||||
|
||||
def test_anonymous_cannot_get(self):
|
||||
response = Client().get(self.url)
|
||||
self.assertRedirects(response, "/login?next={}".format(self.url))
|
||||
|
||||
def test_ernesto_user_can_get(self):
|
||||
client = Client()
|
||||
client.force_login(self.user)
|
||||
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ReponseTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = new_user("toto")
|
||||
self.prop = Proposition.objects.create(name="foo", user=self.user)
|
||||
|
||||
def _url(self, rep):
|
||||
assert rep in Answer.REP_CHOICES
|
||||
return reverse("propositions:{}".format(rep), args=(self.prop.id,))
|
||||
|
||||
def test_anonymous_cannot_get(self):
|
||||
client = Client()
|
||||
|
||||
url = reverse("propositions:oui", args=(self.prop.id,))
|
||||
response = client.get(url)
|
||||
self.assertRedirects(response, "/login?next={}".format(url))
|
||||
|
||||
url = reverse("propositions:non", args=(self.prop.id,))
|
||||
response = client.get(url)
|
||||
self.assertRedirects(response, "/login?next={}".format(url))
|
||||
|
||||
def test_ernesto_user_can_get(self):
|
||||
client = Client()
|
||||
client.force_login(self.user)
|
||||
|
||||
client.get(reverse("propositions:oui", args=(self.prop.id,)))
|
||||
self.prop.refresh_from_db()
|
||||
self.assertEqual(
|
||||
list(self.prop.answer_set.values_list("answer", flat=True)), [Answer.YES],
|
||||
)
|
||||
|
||||
client.get(reverse("propositions:non", args=(self.prop.id,)))
|
||||
self.prop.refresh_from_db()
|
||||
self.assertEqual(
|
||||
list(self.prop.answer_set.values_list("answer", flat=True)), [Answer.NO]
|
||||
)
|
|
@ -1,13 +0,0 @@
|
|||
from django.urls import path
|
||||
|
||||
from propositions import views
|
||||
from propositions.models import Answer
|
||||
|
||||
app_name = "propositions"
|
||||
urlpatterns = [
|
||||
path("", views.PropositionList.as_view(), name="list"),
|
||||
path("new", views.PropositionCreate.as_view(), name="create"),
|
||||
path("<int:id>/oui", views.answer, {"ans": "oui"}, name=Answer.YES),
|
||||
path("<int:id>/non", views.answer, {"ans": "non"}, name=Answer.NO),
|
||||
path("<int:pk>/supprimer", views.PropositionDelete.as_view(), name="delete"),
|
||||
]
|
|
@ -1,8 +0,0 @@
|
|||
import string
|
||||
import random
|
||||
|
||||
|
||||
def generer(*args):
|
||||
caracteres = string.ascii_letters + string.digits
|
||||
aleatoire = [random.choice(caracteres) for _ in range(6)]
|
||||
return ''.join(aleatoire)
|
|
@ -1,63 +0,0 @@
|
|||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.db.models import Count, OuterRef, Q, Subquery
|
||||
from django.views.generic import CreateView, DeleteView, ListView
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from propositions.models import Answer, Proposition
|
||||
|
||||
|
||||
class PropositionCreate(LoginRequiredMixin, CreateView):
|
||||
template_name = "propositions/create.html"
|
||||
success_url = reverse_lazy("propositions:list")
|
||||
model = Proposition
|
||||
fields = ["name", "artist", "link"]
|
||||
|
||||
def form_valid(self, form):
|
||||
proposition = form.save(commit=False)
|
||||
proposition.user = self.request.user
|
||||
proposition.save()
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
class PropositionList(LoginRequiredMixin, ListView):
|
||||
template_name = "propositions/liste.html"
|
||||
context_object_name = "propositions"
|
||||
model = Proposition
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
user_answers = (
|
||||
Answer.objects
|
||||
.filter(proposition=OuterRef("id"), user=user)
|
||||
.values_list("answer", flat=True)
|
||||
)
|
||||
return (
|
||||
Proposition.objects
|
||||
.annotate(nb_yes=Count("answer", filter=Q(answer__answer=Answer.YES)))
|
||||
.annotate(nb_no=Count("answer", filter=Q(answer__answer=Answer.NO)))
|
||||
.annotate(user_answer=Subquery(user_answers[:1]))
|
||||
.order_by("-nb_yes", "nb_no", "name")
|
||||
)
|
||||
|
||||
|
||||
class PropositionDelete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
||||
model = Proposition
|
||||
template_name = "propositions/delete.html"
|
||||
success_url = reverse_lazy("propositions:list")
|
||||
|
||||
def test_func(self):
|
||||
proposition = self.get_object()
|
||||
user = self.request.user
|
||||
return (proposition.user == user or user.profile.is_chef)
|
||||
|
||||
|
||||
@login_required
|
||||
def answer(request, id, ans):
|
||||
proposition = get_object_or_404(Proposition, id=id)
|
||||
user = request.user
|
||||
Answer.objects.filter(proposition=proposition, user=user).delete()
|
||||
Answer.objects.create(proposition=proposition, user=user, answer=ans)
|
||||
return redirect("propositions:list")
|
4
requirements-devel.txt
Normal file
4
requirements-devel.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
-r requirements.txt
|
||||
|
||||
django-debug-toolbar
|
||||
ipython
|
8
requirements.txt
Normal file
8
requirements.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
Django==2.2.*
|
||||
|
||||
# Pour la prod
|
||||
#psycopg2
|
||||
gunicorn
|
||||
django-colorful
|
||||
Pillow
|
||||
django-avatar==5.0.*
|
46
shell.nix
46
shell.nix
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
sources ? import ./npins,
|
||||
pkgs ? import sources.nixpkgs {
|
||||
overlays = [
|
||||
(import "${sources.nix-pkgs}/overlay.nix").default
|
||||
];
|
||||
},
|
||||
dev ? true,
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
packages = [
|
||||
(pkgs.python3.withPackages (
|
||||
ps:
|
||||
[
|
||||
ps.django
|
||||
ps.django-avatar
|
||||
ps.django-colorful
|
||||
ps.gunicorn
|
||||
ps.pillow
|
||||
ps.loadcredential
|
||||
]
|
||||
++ (
|
||||
if dev then
|
||||
[
|
||||
ps.ipython
|
||||
ps.django-debug-toolbar
|
||||
]
|
||||
else
|
||||
[ ]
|
||||
)
|
||||
))
|
||||
];
|
||||
env = {
|
||||
DJANGO_SETTINGS_MODULE = "Ernestophone.settings";
|
||||
|
||||
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
|
||||
|
||||
ERNESTOPHONE_DEBUG = builtins.toJSON true;
|
||||
ERNESTOPHONE_STATIC_ROOT = builtins.toString ./.static;
|
||||
};
|
||||
shellHook = ''
|
||||
if [ ! -d .static ]; then
|
||||
mkdir .static
|
||||
fi
|
||||
'';
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
<p>{% trans "Avatar courant: " %}</p>
|
||||
{% avatar user %}
|
||||
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar:add' %}">
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar_add' %}">
|
||||
{{ upload_avatar_form.as_p }}
|
||||
<p>{% csrf_token %}<input type="submit" value="{% trans "Ajouter une nouvelle image" %}" /></p>
|
||||
</form>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<p>{% trans "Tu n'as pas encore ajouté d'avatar. Le logo Ernestophone sera utilisé par defaut." %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar:add' %}">
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar_add' %}">
|
||||
{{ upload_avatar_form.as_p }}
|
||||
<p>{% csrf_token %}<input type="submit" value="{% trans "Modifier mon avatar" %}" /></p>
|
||||
</form>
|
||||
|
@ -27,7 +27,7 @@
|
|||
|
||||
<a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a>
|
||||
{% if avatars %}
|
||||
<a class="button alt" href="{% url 'avatar:delete' %}">{% trans "Supprimer l'avatar "%}</a>
|
||||
<a class="button alt" href="{% url 'avatar_delete' %}">{% trans "Supprimer l'avatar "%}</a>
|
||||
{% endif %}
|
||||
</div></div></div></section></div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
{% endif %}
|
||||
|
||||
<p>
|
||||
<a class="button alt" href="{% url 'avatar:change' %}">{% trans "Changer l'avatar "%}</a>
|
||||
<a class="button alt" href="{% url 'avatar_change' %}">{% trans "Changer l'avatar "%}</a>
|
||||
{% if request.user|has_avatar %}
|
||||
<a class="button alt" href="{% url 'avatar:delete' %}">{% trans "Supprimer l'avatar "%}</a>
|
||||
<a class="button alt" href="{% url 'avatar_delete' %}">{% trans "Supprimer l'avatar "%}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
<div class="6u 12u$(small)">
|
||||
<h2>{% trans "Suppression de l'avatar" %} :</h2>
|
||||
{% if not avatars %}
|
||||
{% url 'avatar:change' as avatar_change_url %}
|
||||
{% url 'avatar_change' as avatar_change_url %}
|
||||
<p>{% trans "Vous n'avez pas d'avatar à supprimer."%}</p>
|
||||
<a href="{{ avatar_change_url }}" class="button">{% trans "Télécharger un avatar" %}</a> <a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a>
|
||||
{% else %}
|
||||
<p>{% trans "Séléctione l'avatar pour le supprimer" %}</p>
|
||||
<form method="POST" action="{% url 'avatar:delete' %}">
|
||||
<form method="POST" action="{% url 'avatar_delete' %}">
|
||||
|
||||
|
||||
{% for field in delete_avatar_form %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue