add propositions back
This commit is contained in:
parent
5eb27e2171
commit
aa430fa4d2
20 changed files with 639 additions and 0 deletions
|
@ -35,6 +35,7 @@ ACCOUNT_CREATION_PASS = import_secret("ACCOUNT_CREATION_PASS")
|
||||||
BASE_DIR = os.path.join(os.path.dirname(__file__), "..", "..")
|
BASE_DIR = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
"propositions",
|
||||||
"trombonoscope",
|
"trombonoscope",
|
||||||
"actu",
|
"actu",
|
||||||
"colorful",
|
"colorful",
|
||||||
|
|
17
calendrier/migrations/0008_auto_20220322_1454.py
Normal file
17
calendrier/migrations/0008_auto_20220322_1454.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
0
propositions/__init__.py
Normal file
0
propositions/__init__.py
Normal file
0
propositions/admin.py
Normal file
0
propositions/admin.py
Normal file
38
propositions/migrations/0001_initial.py
Normal file
38
propositions/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
18
propositions/migrations/0002_nom_verbose_name.py
Normal file
18
propositions/migrations/0002_nom_verbose_name.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,75 @@
|
||||||
|
# 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"),
|
||||||
|
]
|
43
propositions/migrations/0004_prop_renaming_and_cleaning.py
Normal file
43
propositions/migrations/0004_prop_renaming_and_cleaning.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
24
propositions/migrations/0005_remove_nb_yes_no_fields.py
Normal file
24
propositions/migrations/0005_remove_nb_yes_no_fields.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
53
propositions/migrations/0006_proposition_profile_to_user.py
Normal file
53
propositions/migrations/0006_proposition_profile_to_user.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
19
propositions/migrations/0007_auto_20220322_1455.py
Normal file
19
propositions/migrations/0007_auto_20220322_1455.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
0
propositions/migrations/__init__.py
Normal file
0
propositions/migrations/__init__.py
Normal file
34
propositions/models.py
Normal file
34
propositions/models.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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"
|
14
propositions/templates/propositions/create.html
Normal file
14
propositions/templates/propositions/create.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% 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 %}
|
10
propositions/templates/propositions/delete.html
Normal file
10
propositions/templates/propositions/delete.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% 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 %}
|
44
propositions/templates/propositions/liste.html
Normal file
44
propositions/templates/propositions/liste.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{% 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 %}
|
165
propositions/tests.py
Normal file
165
propositions/tests.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
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]
|
||||||
|
)
|
13
propositions/urls.py
Normal file
13
propositions/urls.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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"),
|
||||||
|
]
|
8
propositions/utils.py
Normal file
8
propositions/utils.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
def generer(*args):
|
||||||
|
caracteres = string.ascii_letters + string.digits
|
||||||
|
aleatoire = [random.choice(caracteres) for _ in range(6)]
|
||||||
|
return ''.join(aleatoire)
|
63
propositions/views.py
Normal file
63
propositions/views.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
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")
|
Loading…
Reference in a new issue