First Commit
This commit is contained in:
commit
ce0758fbbb
496 changed files with 52062 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.venv
|
||||
.env
|
||||
.DS_Store
|
||||
venv
|
||||
staticfiles/**/*
|
||||
media/**/*
|
||||
*.sqlite3
|
||||
**/__pycache__/*
|
||||
82
commons/bbcode_parser.py
Normal file
82
commons/bbcode_parser.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import re
|
||||
|
||||
class BBCodeParser:
|
||||
def __init__(self):
|
||||
# On défini ici les balises BBcode et leur équivalent en html
|
||||
self.bbcode_patterns = {
|
||||
r'\[b\](.*?)\[/b\]': r'<strong>\1</strong>',
|
||||
r'\[i\](.*?)\[/i\]': r'<i>\1</i>',
|
||||
r'\[u\](.*?)\[/u\]': r'<u>\1</u>',
|
||||
r'\[s\](.*?)\[/s\]': r'<strike>\1</strike>',
|
||||
r'\[url=(.*?)\](.*?)\[/url\]': r'<a href="\1">\2</a>',
|
||||
r'\[url\](.*?)\[/url\]': r'<a href="\1">\1</a>',
|
||||
r'\[url=(.*?)(?:\s+class=(.*?))?\](.*?)\[/url\]': lambda m: f'<a href="{m.group(1)}"{f" class={m.group(2)}" if m.group(2) else ""}>{{m.group(3)}}</a>',
|
||||
r'\[url\](?:\s+class=(.*?))?\](.*?)\[/url\]': lambda m: f'<a href="{m.group(2)}"{f" class={m.group(1)}" if m.group(1) else ""}>{{m.group(2)}}</a>',
|
||||
r'\[img alt=(.*?)\](.*?)\[/img\]': r'<img src="\2" alt="\1">',
|
||||
r'\[img\](.*?)\[/img\]': r'<img src="\1" alt="Image insérer par un utilisateur">',
|
||||
r'\[list\](.*?)\[/list\]': r'<ul class="bbcode-list">\1</ul>',
|
||||
r'\[\*\](.*?)': r'<li class="bbcode-list">\1</li>',
|
||||
r'\[t1\](.*?)\[/t1\]': r'<span style="font-size:2rem;font-weight:800;">\1</span>',
|
||||
r'\[t2\](.*?)\[/t2\]': r'<span style="font-size:1.6rem;font-weight:600;">\1</span>',
|
||||
r'\[t3\](.*?)\[/t3\]': r'<span style="font-size:1.4rem;font-weight:400;">\1</span>',
|
||||
r'\[citation\](.*?)\[/citation\]': r'<fieldset class="quote-bbcode">\1</fieldset>',
|
||||
r'\[citation=(.*?)\](.*?)\[/citation\]': r'<fieldset class="quote-bbcode"><legend>\1</legend>\2</fieldset>',
|
||||
r'\[quote\](.*?)\[/quote\]': r'<fieldset class="quote-bbcode">\1</fieldset>',
|
||||
r'\[quote=(.*?)\](.*?)\[/quote\]': r'<fieldset class="quote-bbcode"><legend>\1</legend>\2</fieldset>',
|
||||
r'\[color=(.*?)\](.*?)\[/color\]': r'<span style="color: \1;">\2</span>',
|
||||
r'\[size=(.*?)\](.*?)\[/size\]': r'<span style="font-size: \1px;">\2</span>',
|
||||
r'\[p](.*?)\[/p\]': r'<p>\1</p>',
|
||||
r'\[center\](.*?)\[/center\]': r'<div style="text-align:center;">\1</div>',
|
||||
r'\[right\](.*?)\[/right\]': r'<div style="text-align:right;">\1</div>',
|
||||
r'\[hr\]': r'<hr>',
|
||||
r'\[block\](.*?)\[/block\]': r'<div>\1</div>',
|
||||
r'\[block style=(.*?)\](.*?)\[/block\]': r'<div style="\1">\2</div>',
|
||||
r'\[block class=(.*?)\](.*?)\[/block\]': r'<div class="\1">\2</div>',
|
||||
r'\[block style=(.*?) class=(.*?)\](.*?)\[/block\]': r'<div style="\1" class="\2">\3</div>',
|
||||
r'\[code\](.*?)\[/code\]': lambda m: self._handle_code(m.group(1)),
|
||||
r'\[code=(.*?)\](.*?)\[/code\]': lambda m: self._handle_code(m.group(2), m.group(1)),
|
||||
}
|
||||
|
||||
def _handle_code(self, content, language='none'):
|
||||
# Convertit les retours à la ligne en \n littéraux
|
||||
escaped_content = (
|
||||
content
|
||||
.replace('&', '&')
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('"', '"')
|
||||
.replace("'", ''')
|
||||
.replace('\r\n', '\n') # Normalise les retours à la ligne Windows
|
||||
.replace('\r', '\n') # Normalise les retours à la ligne Mac
|
||||
)
|
||||
# Enveloppe dans pre/code sans modifier les \n
|
||||
return f'''<pre class="language-{language}"><code class="language-{language}">{escaped_content}</code></pre>'''
|
||||
|
||||
def parse(self, text):
|
||||
# Protège temporairement le contenu des balises [code]
|
||||
code_blocks = []
|
||||
def save_code(match):
|
||||
code_blocks.append(match.group(0))
|
||||
return f'@@CODE_BLOCK_{len(code_blocks)-1}@@'
|
||||
|
||||
text = re.sub(r'\[code(?:=.*?)?\].*?\[/code\]', save_code, text, flags=re.DOTALL)
|
||||
|
||||
# Parse les autres BBCodes
|
||||
for bbcode, html in self.bbcode_patterns.items():
|
||||
if callable(html):
|
||||
text = re.sub(bbcode, html, text, flags=re.DOTALL)
|
||||
else:
|
||||
text = re.sub(bbcode, html, text, flags=re.DOTALL)
|
||||
|
||||
# Restaure les blocs de code
|
||||
for i, code_block in enumerate(code_blocks):
|
||||
if '=' in code_block: # code avec langage spécifié
|
||||
lang = re.match(r'\[code=(.*?)\]', code_block).group(1)
|
||||
content = re.search(r'\[code=.*?\](.*?)\[/code\]', code_block, re.DOTALL).group(1)
|
||||
else: # code sans langage
|
||||
lang = 'none'
|
||||
content = re.search(r'\[code\](.*?)\[/code\]', code_block, re.DOTALL).group(1)
|
||||
|
||||
text = text.replace(f'@@CODE_BLOCK_{i}@@', self._handle_code(content, lang))
|
||||
|
||||
return text
|
||||
0
forum/__init__.py
Executable file
0
forum/__init__.py
Executable file
33
forum/admin.py
Executable file
33
forum/admin.py
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
from django.contrib import admin
|
||||
from .models import Category, Forum, Topic, Post
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'description', 'created', 'updated')
|
||||
list_filter = ('created', 'updated')
|
||||
search_fields = ('name', 'description')
|
||||
ordering = ('-created',)
|
||||
fields = ('name', 'description')
|
||||
|
||||
class ForumAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'author', 'category', 'created', 'updated')
|
||||
list_filter = ('category', 'author', 'created', 'updated')
|
||||
search_fields = ('name', 'description')
|
||||
ordering = ('-created',)
|
||||
fields = ('category', 'author', 'name', 'description')
|
||||
|
||||
class TopicAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'author', 'created', 'updated')
|
||||
list_filter = ('forum', 'author', 'created', 'updated', 'state')
|
||||
search_fields = ('title',)
|
||||
ordering = ('-created',)
|
||||
fields = ('forum', 'author', 'title', 'state')
|
||||
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
list_display = ('type', 'topic', 'author', 'created', 'updated')
|
||||
list_filter = ('topic', 'author', 'created', 'updated', 'type')
|
||||
fields = ('topic', 'author', 'content', 'type')
|
||||
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
admin.site.register(Forum, ForumAdmin)
|
||||
admin.site.register(Topic, TopicAdmin)
|
||||
admin.site.register(Post, PostAdmin)
|
||||
6
forum/apps.py
Executable file
6
forum/apps.py
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ForumConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "forum"
|
||||
26
forum/forms.py
Executable file
26
forum/forms.py
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
from django import forms
|
||||
|
||||
class CreateTopic(forms.Form):
|
||||
TOPIC_TYPE_CHOICES = (
|
||||
('normal', 'Normal'),
|
||||
('announce', 'Annonce'),
|
||||
)
|
||||
title = forms.CharField(
|
||||
max_length=150,
|
||||
label='',
|
||||
widget=forms.TextInput(attrs={'placeholder': 'Titre du sujet'})
|
||||
)
|
||||
content = forms.CharField(
|
||||
label='',
|
||||
widget=forms.Textarea(attrs={'placeholder': 'Contenu du sujet'})
|
||||
)
|
||||
type = forms.ChoiceField(choices=TOPIC_TYPE_CHOICES, label='Type de sujet')
|
||||
|
||||
class CreatePost(forms.Form):
|
||||
content = forms.CharField(
|
||||
label='',
|
||||
widget=forms.Textarea(attrs={'placeholder': 'Contenu du message'})
|
||||
)
|
||||
|
||||
class EditPost(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Message")
|
||||
47
forum/middleware.py
Executable file
47
forum/middleware.py
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
from django.utils.deprecation import MiddlewareMixin
|
||||
from .models import Forum, Topic, Post
|
||||
from users.models import User
|
||||
from django.db.models import Count
|
||||
|
||||
class ForumStatsMiddleware(MiddlewareMixin):
|
||||
# On récupère les statistiques du forum pour les affiché dans le menu
|
||||
def process_request(self, request):
|
||||
# Nombre total de forums
|
||||
total_forums = Forum.objects.count()
|
||||
|
||||
# Nombre total de topics
|
||||
total_topics = Topic.objects.count()
|
||||
|
||||
# Nombre total de posts
|
||||
total_posts = Post.objects.count()
|
||||
|
||||
# Utilisateur ayant posté le plus de messages
|
||||
user_with_most_posts = User.objects.annotate(num_posts=Count('post')).order_by('-num_posts').first()
|
||||
|
||||
# Nombre de messages de l'utilisateur ayant posté le plus de messages
|
||||
most_posts = user_with_most_posts.num_posts if user_with_most_posts else 0
|
||||
|
||||
# Utilisateur ayant créé le plus de topics
|
||||
user_with_most_topics = None
|
||||
most_topics = 0
|
||||
for user in User.objects.all():
|
||||
if user.topic_set.count() > most_topics:
|
||||
user_with_most_topics = user
|
||||
most_topics = user.topic_set.count()
|
||||
|
||||
# Dernier message posté
|
||||
last_post = Post.objects.latest('created') if total_posts > 0 else None
|
||||
|
||||
# Derneir topic créé
|
||||
last_topic = Topic.objects.latest('created') if total_topics > 0 else None
|
||||
|
||||
# Ajouter les variables à l'objet request
|
||||
request.total_forums = total_forums
|
||||
request.total_topics = total_topics
|
||||
request.total_posts = total_posts
|
||||
request.user_with_most_posts = user_with_most_posts
|
||||
request.most_posts = most_posts
|
||||
request.user_with_most_topics = user_with_most_topics
|
||||
request.most_topics = most_topics
|
||||
request.last_post = last_post
|
||||
request.last_topic = last_topic
|
||||
98
forum/migrations/0001_initial.py
Executable file
98
forum/migrations/0001_initial.py
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-22 11:02
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Category",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=50)),
|
||||
("description", models.CharField(max_length=100)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Forum",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("name", models.CharField(max_length=50)),
|
||||
("description", models.CharField(max_length=100)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="forum.category"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Topic",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("title", models.CharField(max_length=50)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
("state", models.CharField(default="open", max_length=10)),
|
||||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"forum",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="forum.forum"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Post",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("content", models.CharField(max_length=100)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
("type", models.CharField(default="post", max_length=10)),
|
||||
("active", models.BooleanField(default=True)),
|
||||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="forum_author",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"topic",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="forum.topic"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
51
forum/migrations/0002_alter_category_options_topicread.py
Executable file
51
forum/migrations/0002_alter_category_options_topicread.py
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-22 18:36
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("forum", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="category",
|
||||
options={"verbose_name": "Catégorie", "verbose_name_plural": "Catégories"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TopicRead",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("last_read", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"topic",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="forum.topic"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("user", "topic")},
|
||||
},
|
||||
),
|
||||
]
|
||||
23
forum/migrations/0003_alter_post_topic.py
Executable file
23
forum/migrations/0003_alter_post_topic.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-22 18:49
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("forum", "0002_alter_category_options_topicread"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="post",
|
||||
name="topic",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="posts",
|
||||
to="forum.topic",
|
||||
),
|
||||
),
|
||||
]
|
||||
18
forum/migrations/0004_alter_post_content.py
Normal file
18
forum/migrations/0004_alter_post_content.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-23 16:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('forum', '0003_alter_post_topic'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='post',
|
||||
name='content',
|
||||
field=models.TextField(),
|
||||
),
|
||||
]
|
||||
0
forum/migrations/__init__.py
Executable file
0
forum/migrations/__init__.py
Executable file
87
forum/models.py
Executable file
87
forum/models.py
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
from django.db import models
|
||||
from users.models import User
|
||||
|
||||
class Category(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=50)
|
||||
description = models.CharField(max_length=100)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Catégorie'
|
||||
verbose_name_plural = 'Catégories'
|
||||
|
||||
def __str__(self):
|
||||
return f'ForumCategory({self.id}, {self.name}, {self.description})'
|
||||
|
||||
class Forum(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=50)
|
||||
description = models.CharField(max_length=100)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'Forum({self.id}, {self.category}, {self.name}, {self.description})'
|
||||
|
||||
class Topic(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
forum = models.ForeignKey(Forum, on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=50)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
state = models.CharField(max_length=10, default='open')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.id:
|
||||
self.state = 'open'
|
||||
super(Topic, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f'Topic({self.id}, {self.forum}, {self.title})'
|
||||
|
||||
def is_unread(self, user):
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
|
||||
last_read = TopicRead.objects.filter(
|
||||
user=user,
|
||||
topic=self
|
||||
).first()
|
||||
|
||||
if not last_read:
|
||||
return True
|
||||
|
||||
last_post = Post.objects.filter(topic=self).order_by('-created').first()
|
||||
if not last_post:
|
||||
return False
|
||||
|
||||
return last_post.created > last_read.last_read
|
||||
|
||||
class Post(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts')
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='forum_author')
|
||||
content = models.TextField()
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
type = models.CharField(max_length=10, default='post')
|
||||
active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'Post({self.id}, {self.topic}, {self.author}, {self.content})'
|
||||
|
||||
class TopicRead(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
|
||||
last_read = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['user', 'topic']
|
||||
|
||||
def __str__(self):
|
||||
return f'TopicRead({self.id}, {self.topic}, {self.user})'
|
||||
0
forum/templatetags/__init__.py
Executable file
0
forum/templatetags/__init__.py
Executable file
9
forum/templatetags/_bbcode_tags.py
Normal file
9
forum/templatetags/_bbcode_tags.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django import template
|
||||
from commons.bbcode_parser import BBCodeParser
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name='bbcode')
|
||||
def bbcode(value):
|
||||
parser = BBCodeParser()
|
||||
return parser.parse(value)
|
||||
7
forum/templatetags/forum_extras.py
Executable file
7
forum/templatetags/forum_extras.py
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
7
forum/templatetags/paginator_tag.py
Executable file
7
forum/templatetags/paginator_tag.py
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag('components/paginator.html', takes_context=True)
|
||||
def paginate(context):
|
||||
return context
|
||||
3
forum/tests.py
Executable file
3
forum/tests.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
20
forum/urls.py
Executable file
20
forum/urls.py
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from passion_retro import settings
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.forum_home, name="forum_home"),
|
||||
path("<int:forum_id>/", views.topic_list, name="topic_list"),
|
||||
path("<int:forum_id>/create_topic/", views.create_topic, name="create_topic"),
|
||||
path("<int:forum_id>/<int:topic_id>/", views.topic_detail, name="post_list"),
|
||||
|
||||
path("<int:topic_id>/lock/", views.lock_topic, name="lock_topic"),
|
||||
path("<int:topic_id>/unlock/", views.unlock_topic, name="unlock_topic"),
|
||||
path("<int:forum_id>/<int:topic_id>/deactivate/", views.deactivate_topic, name="deactivate_topic"),
|
||||
path("<int:forum_id>/<int:topic_id>/activate/", views.activate_topic, name="activate_topic"),
|
||||
path("<int:post_id>/deactivate/", views.deactivate_post, name="deactivate_post"),
|
||||
path("<int:post_id>/activate/", views.activate_post, name="activate_post"),
|
||||
path('post/<int:post_id>/edit/', views.edit_post, name='forum_edit_post'),
|
||||
]
|
||||
303
forum/views.py
Executable file
303
forum/views.py
Executable file
|
|
@ -0,0 +1,303 @@
|
|||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.utils import timezone
|
||||
from .models import Category, Forum, Topic, Post, TopicRead
|
||||
from .forms import CreateTopic, CreatePost, EditPost
|
||||
from django.db.models import Max, F
|
||||
from users.decorators import groups_required
|
||||
from users.models import UserLevel
|
||||
|
||||
def forum_home(request):
|
||||
categories = Category.objects.all()
|
||||
forums = Forum.objects.all()
|
||||
|
||||
last_posts = {}
|
||||
count_topics = {}
|
||||
unread_topics = {}
|
||||
|
||||
for forum in forums:
|
||||
# Dernier post du forum avec une seule requête
|
||||
last_post = Post.objects.select_related(
|
||||
'topic',
|
||||
'author',
|
||||
'topic__forum'
|
||||
).filter(
|
||||
topic__forum=forum
|
||||
).order_by('-created').first()
|
||||
|
||||
last_posts[forum.id] = last_post
|
||||
|
||||
# Nombre de topics (pas de changement)
|
||||
count_topic = Topic.objects.filter(forum=forum).count()
|
||||
count_topics[forum.id] = count_topic
|
||||
|
||||
# Optimisation de la vérification des topics non lus
|
||||
if request.user.is_authenticated:
|
||||
# Récupérer le dernier post de chaque topic du forum en une seule requête
|
||||
latest_posts_per_topic = Post.objects.filter(
|
||||
topic__forum=forum
|
||||
).values('topic_id').annotate(
|
||||
last_post_date=Max('created')
|
||||
)
|
||||
|
||||
# Récupérer les dernières lectures de l'utilisateur pour tous les topics du forum
|
||||
topic_reads = TopicRead.objects.filter(
|
||||
user=request.user,
|
||||
topic__forum=forum
|
||||
).values_list('topic_id', 'last_read')
|
||||
|
||||
# Convertir en dictionnaire pour un accès plus rapide
|
||||
read_dates = dict(topic_reads)
|
||||
|
||||
# Un forum est non lu si au moins un topic a un post plus récent que la dernière lecture
|
||||
is_unread = False
|
||||
for post_info in latest_posts_per_topic:
|
||||
topic_id = post_info['topic_id']
|
||||
last_post_date = post_info['last_post_date']
|
||||
|
||||
# Si le topic n'a jamais été lu
|
||||
if topic_id not in read_dates:
|
||||
is_unread = True
|
||||
break
|
||||
|
||||
# Si le dernier post est plus récent que la dernière lecture
|
||||
if last_post_date > read_dates[topic_id]:
|
||||
is_unread = True
|
||||
break
|
||||
|
||||
unread_topics[forum.id] = is_unread
|
||||
else:
|
||||
unread_topics[forum.id] = False
|
||||
|
||||
print(unread_topics)
|
||||
|
||||
context = {
|
||||
'categories': categories,
|
||||
'forums': forums,
|
||||
'last_posts': last_posts,
|
||||
'count_topics': count_topics,
|
||||
'unread_topics': unread_topics,
|
||||
}
|
||||
|
||||
return render(request, "components/forum_home.html", context)
|
||||
|
||||
def topic_list(request, forum_id):
|
||||
forum = Forum.objects.get(id=forum_id)
|
||||
topics = Topic.objects.filter(forum=forum).order_by('-created')
|
||||
paginator = Paginator(topics, 20)
|
||||
|
||||
page_number = request.GET.get('page')
|
||||
topics = paginator.get_page(page_number)
|
||||
|
||||
count_posts = {}
|
||||
last_posts = {}
|
||||
page_numbers = {}
|
||||
unread_topics = {}
|
||||
|
||||
for topic in topics:
|
||||
count_post = Post.objects.filter(topic=topic).count()
|
||||
count_posts[topic.id] = count_post
|
||||
|
||||
last_post = Post.objects.filter(topic=topic).order_by('-created').first()
|
||||
last_posts[topic.id] = last_post
|
||||
|
||||
if request.user.is_authenticated:
|
||||
# Récupérer le dernier post du topic
|
||||
latest_post = Post.objects.filter(topic=topic).aggregate(last_post_date=Max('created'))['last_post_date']
|
||||
|
||||
# Récupérer la dernière lecture de l'utilisateur pour ce topic
|
||||
topic_read = TopicRead.objects.filter(user=request.user, topic=topic).first()
|
||||
|
||||
# Un topic est non lu si le dernier post est plus récent que la dernière lecture
|
||||
if topic_read:
|
||||
unread_topics[topic.id] = latest_post > topic_read.last_read
|
||||
else:
|
||||
unread_topics[topic.id] = True
|
||||
else:
|
||||
unread_topics[topic.id] = False
|
||||
|
||||
if last_post:
|
||||
# Position du dernier message (nombre de messages jusqu'à ce message)
|
||||
post_position = Post.objects.filter(
|
||||
topic=topic,
|
||||
created__lte=last_post.created
|
||||
).count()
|
||||
|
||||
# Calcul du numéro de page (20 messages par page comme dans topic_detail)
|
||||
page_number = (post_position + 19) // 20 # équivalent à ceil(post_position/20)
|
||||
page_numbers[topic.id] = page_number
|
||||
|
||||
context = {
|
||||
'forum': forum,
|
||||
'topics': topics,
|
||||
'count_posts': count_posts,
|
||||
'last_posts': last_posts,
|
||||
'page_numbers': page_numbers,
|
||||
'unread_topics': unread_topics,
|
||||
'is_paginated': topics.has_other_pages(),
|
||||
'page_obj': topics,
|
||||
'paginator': paginator,
|
||||
}
|
||||
|
||||
return render(request, "forum/topic_list.html", context)
|
||||
|
||||
@login_required
|
||||
def create_topic(request, forum_id):
|
||||
if not request.user.is_active:
|
||||
return HttpResponseForbidden("Vous n'êtes pas autorisé à créer un sujet.")
|
||||
|
||||
if request.method == 'POST':
|
||||
topic_form = request.POST
|
||||
# On créer le topic via le model Topic
|
||||
topic = Topic(
|
||||
forum=Forum.objects.get(id=forum_id),
|
||||
author=request.user,
|
||||
title=topic_form['title'],
|
||||
)
|
||||
|
||||
# Puis le premier post du topic
|
||||
post = Post(
|
||||
topic=topic,
|
||||
author=request.user,
|
||||
content=topic_form['content'],
|
||||
)
|
||||
|
||||
topic.save()
|
||||
post.save()
|
||||
messages.success(request, 'Topic créer avec succès.')
|
||||
UserLevel.objects.update(user=request.user, experience=F('experience') + 15)
|
||||
# on renvoie l'utilisateur sur la page du topic contenu l'id du forum et du topic créer
|
||||
return redirect('post_list', forum_id=forum_id, topic_id=topic.id)
|
||||
|
||||
forum = Forum.objects.get(id=forum_id)
|
||||
topic_form = CreateTopic()
|
||||
context = {
|
||||
'forum': forum,
|
||||
'topic_form': topic_form,
|
||||
}
|
||||
return render(request, "forum/create_topic.html", context)
|
||||
|
||||
def topic_detail(request, topic_id, forum_id):
|
||||
if request.method == 'POST':
|
||||
if not request.user.is_active or not request.user.is_authenticated:
|
||||
return HttpResponseForbidden("Vous n'êtes pas autorisé à poster un message.")
|
||||
|
||||
post_form = CreatePost(request.POST)
|
||||
if post_form.is_valid():
|
||||
post = Post(
|
||||
topic=Topic.objects.get(id=topic_id),
|
||||
author=request.user,
|
||||
content=post_form.cleaned_data['content'],
|
||||
)
|
||||
post.save()
|
||||
UserLevel.objects.update(user=request.user, experience=F('experience') + 10)
|
||||
messages.success(request, 'Message posté avec succès.')
|
||||
|
||||
topic = Topic.objects.get(id=topic_id)
|
||||
posts = Post.objects.filter(topic=topic, active=True)
|
||||
paginator = Paginator(posts, 20)
|
||||
|
||||
page_number = request.GET.get('page')
|
||||
posts = paginator.get_page(page_number)
|
||||
|
||||
# Marquer comme lu si l'utilisateur est connecté
|
||||
if request.user.is_authenticated:
|
||||
TopicRead.objects.update_or_create(
|
||||
user=request.user,
|
||||
topic=topic,
|
||||
defaults={'last_read': timezone.now()}
|
||||
)
|
||||
|
||||
# On compte le nombre de posts de l'auteur
|
||||
count_posts = {}
|
||||
for post in posts:
|
||||
count_post = Post.objects.filter(author=post.author).count()
|
||||
count_posts[post.author.id] = count_post
|
||||
|
||||
context = {
|
||||
'topic': topic,
|
||||
'posts': posts,
|
||||
'count_posts': count_posts,
|
||||
'post_form': CreatePost(),
|
||||
'is_paginated': posts.has_other_pages(),
|
||||
'page_obj': posts,
|
||||
'paginator': paginator,
|
||||
}
|
||||
|
||||
return render(request, "forum/topic_detail.html", context)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def lock_topic(request, topic_id):
|
||||
topic = Topic.objects.get(id=topic_id)
|
||||
topic.state = 'closed'
|
||||
topic.save()
|
||||
messages.success(request, 'Sujet verrouillé avec succès.')
|
||||
return redirect('post_list', forum_id=topic.forum.id, topic_id=topic_id)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def unlock_topic(request, topic_id):
|
||||
topic = Topic.objects.get(id=topic_id)
|
||||
topic.state = 'open'
|
||||
topic.save()
|
||||
messages.success(request, 'Sujet déverrouillé avec succès.')
|
||||
return redirect('post_list', forum_id=topic.forum.id, topic_id=topic_id)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def deactivate_post(request, post_id):
|
||||
post = Post.objects.get(id=post_id)
|
||||
post.active = False
|
||||
post.save()
|
||||
messages.success(request, 'Message désactivé avec succès.')
|
||||
return redirect('post_list', forum_id=post.topic.forum.id, topic_id=post.topic.id)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def activate_post(request, post_id):
|
||||
post = Post.objects.get(id=post_id)
|
||||
post.active = True
|
||||
post.save()
|
||||
messages.success(request, 'Message activé avec succès.')
|
||||
return redirect('post_list', forum_id=post.topic.forum.id, topic_id=post.topic.id)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def deactivate_topic(request, topic_id):
|
||||
topic = Topic.objects.get(id=topic_id)
|
||||
topic.active = False
|
||||
topic.save()
|
||||
messages.success(request, 'Sujet désactivé avec succès.')
|
||||
return redirect('topic_list', forum_id=topic.forum.id)
|
||||
|
||||
@groups_required('Modérateur', 'Admininistrateur', 'Super Admin')
|
||||
def activate_topic(request, topic_id):
|
||||
topic = Topic.objects.get(id=topic_id)
|
||||
topic.active = True
|
||||
topic.save()
|
||||
messages.success(request, 'Sujet activé avec succès.')
|
||||
return redirect('topic_list', forum_id=topic.forum.id)
|
||||
|
||||
@login_required
|
||||
def edit_post(request, post_id):
|
||||
post = Post.objects.get(id=post_id)
|
||||
|
||||
# Vérifier si l'utilisateur a le droit d'éditer
|
||||
if not (request.user == post.author or request.user.groups.filter(name__in=['Administrateur', 'Super Admin']).exists()):
|
||||
return HttpResponseForbidden("Vous n'êtes pas autorisé à éditer ce message.")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = EditPost(request.POST)
|
||||
if form.is_valid():
|
||||
post.content = form.cleaned_data['content']
|
||||
post.updated = timezone.now()
|
||||
post.save()
|
||||
messages.success(request, 'Message modifié avec succès.')
|
||||
return redirect('post_list', forum_id=post.topic.forum.id, topic_id=post.topic.id)
|
||||
else:
|
||||
form = EditPost(initial={'content': post.content})
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'post': post
|
||||
}
|
||||
return render(request, "forum/edit_post.html", context)
|
||||
0
gallery/__init__.py
Normal file
0
gallery/__init__.py
Normal file
3
gallery/admin.py
Normal file
3
gallery/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
gallery/apps.py
Normal file
6
gallery/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GalleryConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'gallery'
|
||||
17
gallery/forms.py
Normal file
17
gallery/forms.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from django import forms
|
||||
|
||||
class AddImgGallery(forms.Form):
|
||||
image = forms.FileField(
|
||||
label='Image',
|
||||
required=True,
|
||||
widget=forms.ClearableFileInput(attrs={'class': 'form-inline'})
|
||||
)
|
||||
|
||||
def clean_img(self):
|
||||
img = self.cleaned_data.get('image')
|
||||
if img:
|
||||
if not img.name.lower().endswith(('.jpg', '.jpeg', '.png')):
|
||||
raise forms.ValidationError('Seul les fichiers JPG, JPEG, PNG sont autorisés.')
|
||||
if img.size > 5 * 1024 * 1024:
|
||||
raise forms.ValidationError('La taille de l\'image ne dois pas dépasser 5 Mo.')
|
||||
return img
|
||||
0
gallery/migrations/__init__.py
Normal file
0
gallery/migrations/__init__.py
Normal file
3
gallery/models.py
Normal file
3
gallery/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
gallery/tests.py
Normal file
3
gallery/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
10
gallery/urls.py
Executable file
10
gallery/urls.py
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from passion_retro import settings
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.home_gallery, name="home_gallery"),
|
||||
path("import", views.import_img, name="import"),
|
||||
]
|
||||
52
gallery/views.py
Normal file
52
gallery/views.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from django.shortcuts import render
|
||||
from django.contrib import messages
|
||||
from .forms import AddImgGallery
|
||||
from django.contrib.auth.decorators import login_required
|
||||
import os
|
||||
from django.conf import settings
|
||||
|
||||
@login_required()
|
||||
def home_gallery(request):
|
||||
user_id = request.user.id
|
||||
user_directory = os.path.join(settings.MEDIA_ROOT, 'galleries', str(user_id))
|
||||
img_urls = []
|
||||
|
||||
if os.path.exists(user_directory):
|
||||
for filename in os.listdir(user_directory):
|
||||
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')):
|
||||
img_urls.append(f"{settings.MEDIA_URL}galleries/{user_id}/{filename}")
|
||||
|
||||
return render(request, 'gallery/home.html', {'images': img_urls})
|
||||
|
||||
@login_required()
|
||||
def import_img(request):
|
||||
if request.method == "POST":
|
||||
form = AddImgGallery(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
image = form.clean_img()
|
||||
user_id = request.user.id
|
||||
saved_image_url = save_image_to_gallery(image, user_id)
|
||||
|
||||
messages.success(request, 'Votre image a bien été ajoutée à la galerie')
|
||||
form = AddImgGallery()
|
||||
return render(request, 'gallery/form.html', {'form': form})
|
||||
|
||||
def save_image_to_gallery(image, user_id):
|
||||
user_directory = os.path.join(settings.MEDIA_ROOT, 'galleries', str(user_id))
|
||||
os.makedirs(user_directory, exist_ok=True)
|
||||
|
||||
# Créer un fichier index.html vide pour sécuriser le répertoire
|
||||
index_file_path = os.path.join(user_directory, 'index.html')
|
||||
if not os.path.exists(index_file_path): # Vérifie si le fichier n'existe pas déjà
|
||||
with open(index_file_path, 'w') as index_file:
|
||||
index_file.write('') # Écrit un fichier vide
|
||||
|
||||
# Définir le chemin complet du fichier
|
||||
file_path = os.path.join(user_directory, image.name)
|
||||
|
||||
# Enregistrer le fichier
|
||||
with open(file_path, 'wb+') as destination:
|
||||
for chunk in image.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
return f"{settings.MEDIA_URL}galleries/{user_id}/{image.name}"
|
||||
0
games/__init__.py
Normal file
0
games/__init__.py
Normal file
4
games/admin.py
Normal file
4
games/admin.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from django.contrib import admin
|
||||
from .models import LittleBacCategories
|
||||
|
||||
admin.site.register(LittleBacCategories)
|
||||
6
games/apps.py
Normal file
6
games/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GamesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'games'
|
||||
64
games/migrations/0001_initial.py
Normal file
64
games/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 09:48
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LittleBacCategories',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('description', models.TextField(default='')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LittleBacGames',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=155)),
|
||||
('status', models.CharField(choices=[('waiting', 'En attente'), ('in_progress', 'En cours'), ('finished', 'Terminée')], default='waiting', max_length=20)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LittleBacRounds',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('letter', models.CharField(max_length=1)),
|
||||
('round_counter', models.IntegerField(default=1)),
|
||||
('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_rounds', to='games.littlebacgames')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LittleBacPlayers',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('score', models.IntegerField()),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_players', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LittleBacAnswers',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('answer', models.CharField(max_length=100)),
|
||||
('is_valid', models.BooleanField(default=False)),
|
||||
('point', models.IntegerField(default=0)),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_answers', to='games.littlebaccategories')),
|
||||
('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_answers', to='games.littlebacplayers')),
|
||||
('round', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_answers', to='games.littlebacrounds')),
|
||||
],
|
||||
),
|
||||
]
|
||||
20
games/migrations/0002_littlebacplayers_game.py
Normal file
20
games/migrations/0002_littlebacplayers_game.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 10:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacplayers',
|
||||
name='game',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_players', to='games.littlebacgames'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
22
games/migrations/0003_littlebacgames_author.py
Normal file
22
games/migrations/0003_littlebacgames_author.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 12:10
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('games', '0002_littlebacplayers_game'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='author',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='little_bac_games', to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
18
games/migrations/0004_littlebacplayers_is_ready.py
Normal file
18
games/migrations/0004_littlebacplayers_is_ready.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 12:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0003_littlebacgames_author'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacplayers',
|
||||
name='is_ready',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
18
games/migrations/0005_littlebacgames_players.py
Normal file
18
games/migrations/0005_littlebacgames_players.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 15:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0004_littlebacplayers_is_ready'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='players',
|
||||
field=models.ManyToManyField(related_name='little_bac_games', to='games.littlebacplayers'),
|
||||
),
|
||||
]
|
||||
17
games/migrations/0006_remove_littlebacgames_players.py
Normal file
17
games/migrations/0006_remove_littlebacgames_players.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 15:37
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0005_littlebacgames_players'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='littlebacgames',
|
||||
name='players',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 19:24
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('games', '0006_remove_littlebacgames_players'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='countdown_started',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='countdown_time',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacanswers',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='games.littlebaccategories'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacanswers',
|
||||
name='player',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='games.littlebacplayers'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacanswers',
|
||||
name='round',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='games.littlebacrounds'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacgames',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='games', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacplayers',
|
||||
name='game',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to='games.littlebacgames'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacplayers',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='littlebacrounds',
|
||||
name='game',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rounds', to='games.littlebacgames'),
|
||||
),
|
||||
]
|
||||
18
games/migrations/0008_littlebacgames_countdown_start_time.py
Normal file
18
games/migrations/0008_littlebacgames_countdown_start_time.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-30 19:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0007_littlebacgames_countdown_started_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='countdown_start_time',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-31 08:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0008_littlebacgames_countdown_start_time'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='littlebacgames',
|
||||
name='countdown_start_time',
|
||||
field=models.DateTimeField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
||||
18
games/migrations/0010_littlebacplayers_status.py
Normal file
18
games/migrations/0010_littlebacplayers_status.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-31 12:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0009_alter_littlebacgames_countdown_start_time'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacplayers',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('playing', 'Joue'), ('overed', 'A fini')], default='playing', max_length=20),
|
||||
),
|
||||
]
|
||||
18
games/migrations/0011_littlebacgames_current_phase.py
Normal file
18
games/migrations/0011_littlebacgames_current_phase.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-02 13:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0010_littlebacplayers_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='littlebacgames',
|
||||
name='current_phase',
|
||||
field=models.CharField(blank=True, default='ready_game', max_length=60, null=True),
|
||||
),
|
||||
]
|
||||
0
games/migrations/__init__.py
Normal file
0
games/migrations/__init__.py
Normal file
49
games/models.py
Normal file
49
games/models.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from django.db import models
|
||||
from users.models import User
|
||||
|
||||
class LittleBacGames(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='games')
|
||||
name = models.CharField(max_length=155)
|
||||
status = models.CharField(max_length=20 ,choices=[
|
||||
('waiting', 'En attente'),
|
||||
('in_progress', 'En cours'),
|
||||
('finished', 'Terminée')
|
||||
], default='waiting')
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
countdown_started = models.BooleanField(default=False)
|
||||
countdown_time = models.IntegerField(default=0)
|
||||
countdown_start_time = models.DateTimeField(default=None, null=True, blank=True)
|
||||
current_phase = models.CharField(max_length=60, default="ready_game", null=True, blank=True)
|
||||
|
||||
class LittleBacPlayers(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='players')
|
||||
game = models.ForeignKey(LittleBacGames, on_delete=models.CASCADE, related_name='players')
|
||||
score = models.IntegerField()
|
||||
is_ready = models.BooleanField(default=False)
|
||||
status = models.CharField(max_length=20, choices=[
|
||||
('playing', 'Joue'),
|
||||
('overed', 'A fini')
|
||||
], default="playing")
|
||||
|
||||
class LittleBacCategories(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(default="")
|
||||
|
||||
class LittleBacRounds(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
game = models.ForeignKey(LittleBacGames, on_delete=models.CASCADE, related_name='rounds')
|
||||
letter = models.CharField(max_length=1)
|
||||
round_counter = models.IntegerField(default=1)
|
||||
|
||||
class LittleBacAnswers(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
round = models.ForeignKey(LittleBacRounds, on_delete=models.CASCADE, related_name='answers')
|
||||
player = models.ForeignKey(LittleBacPlayers, on_delete=models.CASCADE, related_name='answers')
|
||||
category = models.ForeignKey(LittleBacCategories, on_delete=models.CASCADE, related_name='answers')
|
||||
answer = models.CharField(max_length=100)
|
||||
is_valid = models.BooleanField(default=False)
|
||||
point = models.IntegerField(default=0)
|
||||
7
games/templatetags/custom_filters.py
Normal file
7
games/templatetags/custom_filters.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
3
games/tests.py
Normal file
3
games/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
25
games/urls.py
Normal file
25
games/urls.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
# API REST
|
||||
path('api/<int:game_id>/start_countdown', views.game_start_countdown, name='game_start_countdown'),
|
||||
path('api/<int:game_id>/countdown_status', views.game_countdown_status, name='game_countdown_status'),
|
||||
path('api/bac/<int:game_id>/info', views.game_infos_little_bac, name='game_infos_little_bac'),
|
||||
path('api/bac/<int:game_id>/info_party', views.party_infos_little_bac, name='party_infos_little_bac'),
|
||||
path('api/bac/<int:game_id>/players', views.game_players_little_bac, name='game_players_little_bac'),
|
||||
path('api/bac/<int:game_id>/end_game', views.game_liitle_bac_end_game, name='end_game'),
|
||||
path('api/bac/<int:game_id>/player/<int:player_id>/toggle_ready', views.toggle_ready_status_little_bac, name='toggle_ready_status_little_bac'),
|
||||
|
||||
|
||||
path("", views.portal, name="portal_games"),
|
||||
path("bac", views.little_bac_home, name="bac_games"),
|
||||
path("bac/party", views.little_bac_start, name="bac_start_games"),
|
||||
path("bac/party/<str:party_id>", views.little_bac_party, name="bac_party_games"),
|
||||
path("bac/party/<str:party_id>/join", views.little_bac_party_join, name="bac_party_join_games"),
|
||||
path("bac/party/<str:party_id>/play", views.little_bac_party_play, name="bac_party_play_games"),
|
||||
path("bac/party/<str:party_id>/results", views.game_little_bac_results, name="bac_party_results_games"),
|
||||
path('party/<int:game_id>/new_round/', views.game_little_bac_start_new_round, name='bac_start_new_round'),
|
||||
|
||||
]
|
||||
433
games/views.py
Normal file
433
games/views.py
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import JsonResponse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import *
|
||||
from users.models import UserLevel
|
||||
from django.utils.timezone import now
|
||||
from django.contrib import messages
|
||||
from django.db.models import Count, Sum, F
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
def portal(request):
|
||||
last_party = LittleBacGames.objects.filter().last()
|
||||
nb_parties = LittleBacGames.objects.filter(status="finished").count()
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return render(request, 'games/portal.html', {'last_party': last_party, 'nb_parties': nb_parties})
|
||||
games = LittleBacGames.objects.filter(author=request.user, status='waiting')
|
||||
|
||||
return render(request, 'games/portal.html', {'games': games, 'last_party': last_party, 'nb_parties': nb_parties})
|
||||
|
||||
def little_bac_home(request):
|
||||
return render(request, 'games/littlebac/home.html')
|
||||
|
||||
@login_required()
|
||||
def little_bac_start(request):
|
||||
import random
|
||||
import string
|
||||
|
||||
game = LittleBacGames.objects.create(name=f"Partie de {request.user.username}", author=request.user)
|
||||
LittleBacPlayers.objects.create(
|
||||
user=request.user,
|
||||
game=game,
|
||||
score=0
|
||||
)
|
||||
|
||||
# Liste des lettres de l'alphabet
|
||||
alphabet = string.ascii_uppercase # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
||||
# Sélection aléatoire
|
||||
random_letter = random.choice(alphabet)
|
||||
|
||||
LittleBacRounds.objects.create(
|
||||
game=game,
|
||||
letter=random_letter,
|
||||
round_counter=1
|
||||
)
|
||||
|
||||
return redirect('bac_party_games', party_id=game.id)
|
||||
|
||||
@login_required()
|
||||
def little_bac_party(request, party_id):
|
||||
game = LittleBacGames.objects.get(id=party_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
rounds = LittleBacRounds.objects.filter(game=game)
|
||||
|
||||
if players.filter(user=request.user).exists():
|
||||
current_round = rounds.last()
|
||||
return render(request, 'games/littlebac/game.html', {'game': game, 'players': players, 'round': current_round})
|
||||
else:
|
||||
return redirect('bac_games')
|
||||
|
||||
@login_required()
|
||||
def little_bac_party_join(request, party_id):
|
||||
game = LittleBacGames.objects.get(id=party_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
|
||||
if players.filter(user=request.user).exists():
|
||||
return redirect('bac_games')
|
||||
|
||||
LittleBacPlayers.objects.create(
|
||||
user=request.user,
|
||||
game=game,
|
||||
score=0
|
||||
)
|
||||
|
||||
return redirect('bac_party_games', party_id=game.id)
|
||||
|
||||
@login_required()
|
||||
def little_bac_party_play(request, party_id):
|
||||
game = LittleBacGames.objects.get(id=party_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
|
||||
if not players.filter(user=request.user).exists():
|
||||
return redirect('bac_games')
|
||||
|
||||
# Vérifie si la partie est en attente et met à jour le statut
|
||||
if game.status == 'waiting':
|
||||
game.status = 'in_progress'
|
||||
game.save()
|
||||
|
||||
# Récupère le round actuel (dernier round)
|
||||
current_round = LittleBacRounds.objects.filter(game=game).last()
|
||||
categories = LittleBacCategories.objects.all()
|
||||
player = players.get(user=request.user)
|
||||
|
||||
if request.method == "POST":
|
||||
print("POST")
|
||||
# On vérifie que les réponses commencent par la lettre du round
|
||||
round_letter = current_round.letter.upper()
|
||||
all_valid = True
|
||||
for category in categories:
|
||||
answer = request.POST.get(f"col-{category.id}", "").strip()
|
||||
if answer and answer[0].upper() != round_letter:
|
||||
all_valid = False
|
||||
break
|
||||
|
||||
if all_valid:
|
||||
player = LittleBacPlayers.objects.get(game=game, user_id=request.user.id)
|
||||
player.status = "overed"
|
||||
player.save()
|
||||
|
||||
# Récupérer les réponses soumises par le joueur
|
||||
answers = {
|
||||
f"col-{category.id}": request.POST.get(f"col-{category.id}")
|
||||
for category in categories
|
||||
}
|
||||
|
||||
responses = []
|
||||
# Enregistre chaque réponse en base de données
|
||||
for category in categories:
|
||||
answer = answers.get(f"col-{category.id}", "").strip() # Récupère la réponse ou une chaîne vide
|
||||
if answer: # Vérifie si une réponse est fournie
|
||||
response = LittleBacAnswers.objects.create(
|
||||
round=current_round,
|
||||
player=players.get(user=request.user),
|
||||
category=category,
|
||||
answer=answer,
|
||||
is_valid=False
|
||||
)
|
||||
responses.append(response)
|
||||
|
||||
return render(request, 'games/littlebac/finish.html', {
|
||||
'responses': responses,
|
||||
'round': current_round,
|
||||
'categories': categories,
|
||||
'player': player
|
||||
})
|
||||
else:
|
||||
messages.error(request, "Les réponses doivent commencer par la lettre du tour.")
|
||||
return render(request, 'games/littlebac/play.html', {
|
||||
'game': game,
|
||||
'round': current_round,
|
||||
'categories': categories,
|
||||
'countdown_remaining': countdown_remaining,
|
||||
})
|
||||
|
||||
# Passe les informations du décompte au template
|
||||
countdown_remaining = max(
|
||||
0, game.countdown_time - int((now() - game.countdown_start_time).total_seconds())
|
||||
) if game.countdown_started else None
|
||||
|
||||
return render(request, 'games/littlebac/play.html', {
|
||||
'game': game,
|
||||
'round': current_round,
|
||||
'categories': categories,
|
||||
'countdown_remaining': countdown_remaining,
|
||||
'player': player
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def game_little_bac_results(request, party_id):
|
||||
game = get_object_or_404(LittleBacGames, id=party_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
rounds = LittleBacRounds.objects.filter(game=game)
|
||||
categories = LittleBacCategories.objects.all()
|
||||
|
||||
all_organized_answers = {}
|
||||
scores_by_round = {round.id: {} for round in rounds}
|
||||
total_scores = {player.id: 0 for player in players}
|
||||
|
||||
for round in rounds:
|
||||
answers = LittleBacAnswers.objects.filter(round=round)
|
||||
|
||||
# On détermine qu'un mot est valide s'il est unique pour une catégorie donnée
|
||||
for category in categories:
|
||||
valid_answers = answers.filter(category=category).annotate(
|
||||
lower_answer=Lower('answer')
|
||||
).values('lower_answer').annotate(
|
||||
count=Count('lower_answer')
|
||||
).filter(count=1)
|
||||
for answer in valid_answers:
|
||||
answers.filter(category=category, answer__iexact=answer['lower_answer']).update(is_valid=True, point=5)
|
||||
|
||||
# Marquer les réponses dupliquées et leur attribuer 1 point
|
||||
duplicate_answers = answers.filter(category=category).annotate(
|
||||
lower_answer=Lower('answer')
|
||||
).values('lower_answer').annotate(
|
||||
count=Count('lower_answer')
|
||||
).filter(count__gt=1)
|
||||
for answer in duplicate_answers:
|
||||
answers.filter(category=category, answer__iexact=answer['lower_answer']).update(is_valid=False, point=1)
|
||||
|
||||
# Calcule des points pour chaque joueur pour ce round
|
||||
for player in players:
|
||||
player_score = answers.filter(
|
||||
player=player
|
||||
).aggregate(
|
||||
total=Sum('point')
|
||||
)['total'] or 0
|
||||
|
||||
|
||||
scores_by_round[round.id][player.id] = player_score
|
||||
total_scores[player.id] += player_score
|
||||
|
||||
# On ajoute X experience au joueur (X: score du round)
|
||||
user_level = UserLevel.objects.get(user=player.user)
|
||||
last_round_updated = request.session.get(f'last_round_updated_{player.user.id}', 0)
|
||||
if last_round_updated < round.id:
|
||||
user_level.experience += player_score
|
||||
user_level.save()
|
||||
request.session[f'last_round_updated_{player.user.id}'] = round.id
|
||||
|
||||
# Organiser les réponses par joueur et par catégorie pour chaque round
|
||||
for round in rounds:
|
||||
answers = LittleBacAnswers.objects.filter(round=round)
|
||||
organized_answers = {}
|
||||
for player in players:
|
||||
organized_answers[player.id] = {}
|
||||
for category in categories:
|
||||
answer = answers.filter(player=player, category=category).first()
|
||||
if answer:
|
||||
organized_answers[player.id][category.id] = answer.answer
|
||||
else:
|
||||
organized_answers[player.id][category.id] = ""
|
||||
all_organized_answers[round.id] = organized_answers
|
||||
|
||||
# Mettre à jour les scores totaux des joueurs
|
||||
for player in players:
|
||||
player.score = total_scores[player.id]
|
||||
player.save()
|
||||
|
||||
return render(request, 'games/littlebac/results.html', {
|
||||
'game': game,
|
||||
'players': players,
|
||||
'rounds': rounds,
|
||||
'categories': categories,
|
||||
'all_organized_answers': all_organized_answers,
|
||||
'scores_by_round': scores_by_round,
|
||||
'total_scores': total_scores
|
||||
})
|
||||
|
||||
login_required()
|
||||
def game_little_bac_start_new_round(request, game_id):
|
||||
import random
|
||||
import string
|
||||
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
if game.author != request.user:
|
||||
return redirect('bac_party_games', party_id=game_id)
|
||||
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
|
||||
game.status = 'waiting'
|
||||
game.countdown_started = False
|
||||
game.countdown_start_time = None
|
||||
game.countdown_time = 0
|
||||
game.current_phase = "ready_game"
|
||||
game.save()
|
||||
|
||||
players.update(is_ready=False, status='playing')
|
||||
|
||||
# Sélectionne une lettre aléatoire pour le nouveau round
|
||||
alphabet = string.ascii_uppercase
|
||||
letter = random.choice(alphabet)
|
||||
|
||||
# Détermine le numéro du nouveau round
|
||||
round_counter = game.rounds.count() + 1
|
||||
|
||||
# Crée un nouveau round
|
||||
new_round = LittleBacRounds.objects.create(
|
||||
game=game,
|
||||
letter=letter,
|
||||
round_counter=round_counter
|
||||
)
|
||||
|
||||
return redirect('bac_party_games', party_id=game_id)
|
||||
|
||||
# API REST DES JEUX
|
||||
@login_required()
|
||||
def game_players_little_bac(request, game_id):
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
players_list = [{"id": player.id, "username": player.user.username, "score": player.score} for player in players]
|
||||
return JsonResponse({"game_id": game_id, "players": players_list})
|
||||
except LittleBacGames.DoesNotExist:
|
||||
return JsonResponse({"error": "Game not found"}, status=404)
|
||||
|
||||
@login_required()
|
||||
def toggle_ready_status_little_bac(request, game_id, player_id):
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
player = LittleBacPlayers.objects.get(game=game, user_id=player_id)
|
||||
|
||||
player.is_ready = not player.is_ready
|
||||
player.save()
|
||||
|
||||
# Vérifie si tous les joueurs de la partie sont prêts
|
||||
game = player.game
|
||||
print("Joueurs de la partie",game)
|
||||
all_ready = LittleBacPlayers.objects.filter(game=game, is_ready=True).count()
|
||||
print(all_ready)
|
||||
|
||||
return JsonResponse({"is_ready": player.is_ready, "all_ready": all_ready})
|
||||
except LittleBacPlayers.DoesNotExist:
|
||||
return JsonResponse({"error": "Player not found"}, status=404)
|
||||
|
||||
def game_infos_little_bac(request, game_id):
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
|
||||
return JsonResponse({
|
||||
"name": game.name,
|
||||
"status": game.status,
|
||||
"created": game.created,
|
||||
"all_ready": game.players.filter(is_ready=True).count()
|
||||
})
|
||||
except LittleBacGames.DoesNotExist:
|
||||
return JsonResponse({"error": "Party not found"}, status=404)
|
||||
|
||||
@login_required()
|
||||
def party_infos_little_bac(request, game_id):
|
||||
from django.utils.timezone import now
|
||||
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
players = LittleBacPlayers.objects.filter(game=game)
|
||||
players_data = [
|
||||
{"id": player.id, "username": player.user.username, "status": player.status, "score": player.score}
|
||||
for player in players
|
||||
]
|
||||
|
||||
countdown_remaining = None
|
||||
if game.countdown_started and game.countdown_start_time:
|
||||
elapsed_time = (now() - game.countdown_start_time).total_seconds()
|
||||
countdown_remaining = max(0, game.countdown_time - int(elapsed_time))
|
||||
|
||||
print(game.status)
|
||||
|
||||
return JsonResponse({
|
||||
"game_status": game.status,
|
||||
"players": players_data,
|
||||
"countdown_time": countdown_remaining,
|
||||
"countdown_started": game.countdown_started,
|
||||
"current_phase": game.current_phase if hasattr(game, 'current_phase') else None
|
||||
})
|
||||
except LittleBacGames.DoesNotExist:
|
||||
return JsonResponse({"error": "Game not found"}, status=404)
|
||||
|
||||
@login_required()
|
||||
def game_start_countdown(request, game_id):
|
||||
from django.utils.timezone import now
|
||||
|
||||
# Récupère le type de décompte (ready_game ou finish_game)
|
||||
countdown_type = request.GET.get("type", "ready_game")
|
||||
print(f"Type de décompte reçu: {countdown_type}") # Ajout de cette ligne pour vérifier le type de décompte
|
||||
|
||||
if countdown_type not in ["ready_game", "finish_game"]:
|
||||
return JsonResponse({"success": False, "error": "Type de décompte invalide."}, status=400)
|
||||
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
print(f"Statut du jeu: {game.status}") # Ajout de cette ligne pour vérifier le statut du jeu
|
||||
|
||||
# Vérification des conditions pour chaque décompte
|
||||
if countdown_type == "ready_game" and not game.countdown_started and game.status == "waiting":
|
||||
game.countdown_started = True
|
||||
game.countdown_start_time = now()
|
||||
game.countdown_time = 5
|
||||
game.save()
|
||||
|
||||
elif countdown_type == "finish_game" and game.status == "in_progress":
|
||||
print("Finish game")
|
||||
# Vérifie si un décompte précédent n'est pas actif
|
||||
elapsed_time = (now() - game.countdown_start_time).total_seconds()
|
||||
if not game.countdown_started or elapsed_time >= game.countdown_time:
|
||||
print("Start countdown")
|
||||
game.countdown_started = True
|
||||
game.countdown_start_time = now()
|
||||
game.countdown_time = 60
|
||||
game.current_phase = "finish_game"
|
||||
game.save()
|
||||
else:
|
||||
return JsonResponse({"success": False, "error": "Décompte déjà en cours."}, status=400)
|
||||
|
||||
else:
|
||||
return JsonResponse({"success": False, "error": "Condition de décompte non remplie."}, status=400)
|
||||
|
||||
return JsonResponse({
|
||||
"success": True,
|
||||
"countdown_started": game.countdown_started,
|
||||
"countdown_time": game.countdown_time
|
||||
})
|
||||
except LittleBacGames.DoesNotExist:
|
||||
return JsonResponse({"success": False, "error": "Game not found"}, status=404)
|
||||
|
||||
def game_countdown_status(request, game_id):
|
||||
from django.utils.timezone import now
|
||||
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
|
||||
if game.countdown_started and game.countdown_start_time:
|
||||
# Temps écoulé en secondes
|
||||
elapsed_time = (now() - game.countdown_start_time).total_seconds()
|
||||
remaining_time = max(0, game.countdown_time - int(elapsed_time))
|
||||
print(f"Elapsed time: {elapsed_time}, Remaining time: {remaining_time}") # Vérification
|
||||
else:
|
||||
print("Ouuups")
|
||||
remaining_time = game.countdown_time
|
||||
|
||||
return JsonResponse({
|
||||
"countdown_started": game.countdown_started,
|
||||
"countdown_time": remaining_time
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def game_liitle_bac_end_game(request, game_id):
|
||||
from django.utils.timezone import now
|
||||
|
||||
try:
|
||||
game = LittleBacGames.objects.get(id=game_id)
|
||||
|
||||
# Mettre à jour l'état du jeu à "finished"
|
||||
game.status = 'finished'
|
||||
game.updated = now()
|
||||
game.save()
|
||||
|
||||
# Optionnel : Mettez à jour tous les joueurs pour les marquer comme ayant terminé
|
||||
LittleBacPlayers.objects.filter(game=game).update(status='overed')
|
||||
|
||||
return JsonResponse({"success": True, "message": "Game ended successfully."})
|
||||
except LittleBacGames.DoesNotExist:
|
||||
return JsonResponse({"error": "Game not found"}, status=404)
|
||||
0
guestbook/__init__.py
Executable file
0
guestbook/__init__.py
Executable file
3
guestbook/admin.py
Executable file
3
guestbook/admin.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
guestbook/apps.py
Executable file
6
guestbook/apps.py
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GuestbookConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "guestbook"
|
||||
12
guestbook/forms.py
Executable file
12
guestbook/forms.py
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
from django import forms
|
||||
|
||||
class CreateGuestbook(forms.Form):
|
||||
author = forms.CharField(
|
||||
max_length=150,
|
||||
label='',
|
||||
widget=forms.TextInput(attrs={'placeholder': 'Nom'})
|
||||
)
|
||||
content = forms.CharField(
|
||||
label='',
|
||||
widget=forms.Textarea(attrs={'placeholder': 'Message'})
|
||||
)
|
||||
14
guestbook/middleware.py
Executable file
14
guestbook/middleware.py
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
from django.utils.deprecation import MiddlewareMixin
|
||||
from guestbook.models import Guestbook
|
||||
|
||||
class GuestbookMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
# On récupère les messages du livre d'or et les auteurs
|
||||
guestbook = Guestbook.objects.all().order_by('-created')[:5]
|
||||
|
||||
# On compte le nombre de messages
|
||||
total_guestbook = Guestbook.objects.count()
|
||||
|
||||
# On ajoute les variables à l'objet request
|
||||
request.guestbook = guestbook
|
||||
request.total_guestbook = total_guestbook
|
||||
23
guestbook/migrations/0001_initial.py
Executable file
23
guestbook/migrations/0001_initial.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-22 11:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Guestbook",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
("author", models.CharField(default="Visiteur", max_length=50)),
|
||||
("content", models.CharField(max_length=100)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("active", models.BooleanField(default=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
guestbook/migrations/__init__.py
Executable file
0
guestbook/migrations/__init__.py
Executable file
11
guestbook/models.py
Executable file
11
guestbook/models.py
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
from django.db import models
|
||||
|
||||
class Guestbook(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
author = models.CharField(max_length=50, default='Visiteur')
|
||||
content = models.CharField(max_length=100)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'Guestbook({self.id}, {self.author}, {self.content}, {self.created})'
|
||||
3
guestbook/tests.py
Executable file
3
guestbook/tests.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
guestbook/urls.py
Executable file
9
guestbook/urls.py
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from passion_retro import settings
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.guestbook_home, name="guestbook_home"),
|
||||
]
|
||||
29
guestbook/views.py
Executable file
29
guestbook/views.py
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import render
|
||||
from .models import Guestbook
|
||||
from .forms import CreateGuestbook
|
||||
|
||||
def guestbook_home(request):
|
||||
# Si un message est envoyé on le traite
|
||||
if request.method == "POST":
|
||||
form = CreateGuestbook(request.POST)
|
||||
if form.is_valid():
|
||||
author = form.cleaned_data['author']
|
||||
content = form.cleaned_data['content']
|
||||
Guestbook.objects.create(author=author, content=content)
|
||||
|
||||
guestbook = Guestbook.objects.all().order_by('-created')
|
||||
paginator = Paginator(guestbook, 10)
|
||||
|
||||
page_number = request.GET.get('page')
|
||||
guestbook = paginator.get_page(page_number)
|
||||
|
||||
context = {
|
||||
'guestbook': guestbook,
|
||||
'is_paginated': guestbook.has_other_pages(),
|
||||
'page_obj': guestbook,
|
||||
'paginator': paginator,
|
||||
'form': CreateGuestbook(),
|
||||
}
|
||||
|
||||
return render(request, "components/guestbook_home.html", context)
|
||||
0
home/__init__.py
Executable file
0
home/__init__.py
Executable file
3
home/admin.py
Executable file
3
home/admin.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
home/apps.py
Executable file
6
home/apps.py
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "home"
|
||||
0
home/migrations/__init__.py
Executable file
0
home/migrations/__init__.py
Executable file
3
home/models.py
Executable file
3
home/models.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
home/tests.py
Executable file
3
home/tests.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
12
home/urls.py
Executable file
12
home/urls.py
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from passion_retro import settings
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.home, name="home"),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
16
home/views.py
Executable file
16
home/views.py
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
from django.shortcuts import render
|
||||
from posts.models import Post
|
||||
|
||||
def home(request):
|
||||
edito = Post.objects.filter(type='edito', active=True).first()
|
||||
news = Post.objects.filter(type='news', active=True).order_by('-created')[:6]
|
||||
|
||||
context = {
|
||||
'edito': edito,
|
||||
'news': news,
|
||||
}
|
||||
|
||||
return render(request, "home.html", context)
|
||||
|
||||
def custom_404(request, exception):
|
||||
return render(request, "errors/404.html", status=404)
|
||||
0
maintenance/__init__.py
Normal file
0
maintenance/__init__.py
Normal file
4
maintenance/admin.py
Normal file
4
maintenance/admin.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from django.contrib import admin
|
||||
from .models import *
|
||||
|
||||
admin.site.register(Informations)
|
||||
6
maintenance/apps.py
Normal file
6
maintenance/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MaintenanceConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'maintenance'
|
||||
22
maintenance/middleware.py
Normal file
22
maintenance/middleware.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth import get_user
|
||||
|
||||
from .models import Informations
|
||||
|
||||
class MaintenanceMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if not request.user.is_superuser and not request.path.startswith('/admin'):
|
||||
try:
|
||||
maintenance_info = Informations.objects.get(pk=1)
|
||||
except Informations.DoesNotExist:
|
||||
maintenance_info = None
|
||||
|
||||
if maintenance_info and maintenance_info.is_active == True and not request.path.startswith(reverse('maintenance:info')):
|
||||
return redirect(reverse('maintenance:info'))
|
||||
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
23
maintenance/migrations/0001_initial.py
Normal file
23
maintenance/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-26 19:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Informations',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(default='Maintenance en cours', max_length=255, verbose_name='Titre de la maintenance')),
|
||||
('content', models.TextField(verbose_name='Contenu de la maintenance')),
|
||||
('is_active', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 17:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('maintenance', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='informations',
|
||||
name='content',
|
||||
field=models.TextField(default='Votre site rétro favoris fait un chek-up ;)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='informations',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
0
maintenance/migrations/__init__.py
Normal file
0
maintenance/migrations/__init__.py
Normal file
10
maintenance/models.py
Normal file
10
maintenance/models.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from django.db import models
|
||||
|
||||
class Informations(models.Model):
|
||||
name = models.CharField("Titre de la maintenance", max_length=255, default="Maintenance en cours")
|
||||
content = models.TextField(default="Votre site rétro favoris fait un chek-up ;)")
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return "Contenu de la maintenance"
|
||||
|
||||
3
maintenance/tests.py
Normal file
3
maintenance/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
maintenance/urls.py
Normal file
7
maintenance/urls.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "maintenance"
|
||||
urlpatterns = [
|
||||
path('', views.info, name="info"),
|
||||
]
|
||||
6
maintenance/views.py
Normal file
6
maintenance/views.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.shortcuts import render, get_object_or_404
|
||||
from .models import Informations
|
||||
|
||||
def info(request):
|
||||
message = get_object_or_404(Informations, pk=1)
|
||||
return render(request, 'maintenance/index.html', {'message': message})
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passion_retro.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
messagerie/__init__.py
Normal file
0
messagerie/__init__.py
Normal file
3
messagerie/admin.py
Normal file
3
messagerie/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
messagerie/apps.py
Normal file
6
messagerie/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MessagerieConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'messagerie'
|
||||
24
messagerie/context_processors.py
Normal file
24
messagerie/context_processors.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from .models import PrivateMessageSubject, PrivateMessage
|
||||
from django.db.models import Q
|
||||
|
||||
def pending_pm_count(request):
|
||||
if request.user.is_authenticated:
|
||||
# Filtrer les sujets de messages où l'utilisateur est soit le receiver soit le sender
|
||||
pm_subjects = PrivateMessageSubject.objects.filter(
|
||||
Q(receiver=request.user) | Q(sender=request.user)
|
||||
)
|
||||
|
||||
# Initialiser le compteur de messages non lus
|
||||
count = 0
|
||||
|
||||
# Parcourir chaque sujet de message
|
||||
for subject in pm_subjects:
|
||||
# Récupérer le dernier message du sujet
|
||||
last_message = subject.messages.order_by('-date_sent').first()
|
||||
|
||||
# Vérifier si le dernier message n'est pas de l'utilisateur actuel et si le sujet n'est pas lu
|
||||
if last_message and last_message.author != request.user and not subject.is_read:
|
||||
count += 1
|
||||
|
||||
return {'pending_pm_count': count}
|
||||
return {'pending_pm_count': 0}
|
||||
23
messagerie/forms.py
Normal file
23
messagerie/forms.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Forms pour la messagerie
|
||||
from django import forms
|
||||
from .models import PrivateMessageSubject, PrivateMessage
|
||||
|
||||
class PrivateMessageSubjectForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PrivateMessageSubject
|
||||
fields = ['receiver', 'subject']
|
||||
labels = {
|
||||
'receiver': 'Destinataire',
|
||||
'subject': 'Sujet'
|
||||
}
|
||||
|
||||
class PrivateMessageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PrivateMessage
|
||||
fields = ['message']
|
||||
labels = {
|
||||
'message': ''
|
||||
}
|
||||
widgets = {
|
||||
'message': forms.Textarea(attrs={'placeholder': 'Votre message'})
|
||||
}
|
||||
34
messagerie/migrations/0001_initial.py
Normal file
34
messagerie/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 10:49
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PrivateMessage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('subject', models.CharField(max_length=100)),
|
||||
('message', models.TextField()),
|
||||
('date_sent', models.DateTimeField(auto_now_add=True)),
|
||||
('is_read', models.BooleanField(default=False)),
|
||||
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Message privé',
|
||||
'verbose_name_plural': 'Messages privés',
|
||||
'ordering': ['-date_sent'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 11:33
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('messagerie', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='privatemessage',
|
||||
name='is_read',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='privatemessage',
|
||||
name='receiver',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='privatemessage',
|
||||
name='sender',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='privatemessage',
|
||||
name='author',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='author_messages', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='privatemessage',
|
||||
name='id',
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PrivateMessageSubject',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('subject', models.CharField(max_length=100)),
|
||||
('is_read', models.BooleanField(default=False)),
|
||||
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='privatemessage',
|
||||
name='subject',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='messagerie.privatemessagesubject'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 13:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('messagerie', '0002_remove_privatemessage_is_read_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='privatemessagesubject',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
0
messagerie/migrations/__init__.py
Normal file
0
messagerie/migrations/__init__.py
Normal file
28
messagerie/models.py
Normal file
28
messagerie/models.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from django.db import models
|
||||
from users.models import User
|
||||
|
||||
# Création d'une messagerie privée pour les membres
|
||||
class PrivateMessageSubject(models.Model):
|
||||
receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_messages')
|
||||
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages')
|
||||
subject = models.CharField(max_length=100)
|
||||
is_read = models.BooleanField(default=False)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'Sujet: {self.subject} (De: {self.sender.username} À: {self.receiver.username})'
|
||||
|
||||
class PrivateMessage(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
subject = models.ForeignKey(PrivateMessageSubject, on_delete=models.CASCADE, related_name='messages')
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_messages', default=1)
|
||||
message = models.TextField()
|
||||
date_sent = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.author.username} ({self.date_sent})'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Message privé'
|
||||
verbose_name_plural = 'Messages privés'
|
||||
ordering = ['-date_sent']
|
||||
3
messagerie/tests.py
Normal file
3
messagerie/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
13
messagerie/urls.py
Normal file
13
messagerie/urls.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# urls messagerie
|
||||
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home, name='pm_home'),
|
||||
path('view/<int:message_id>/', views.view_message, name='pm_view'),
|
||||
path('new/', views.new_message, name='pm_new'),
|
||||
path('delete/<int:message_id>/', views.delete_subject, name='pm_delete'),
|
||||
|
||||
path('send_all_users/', views.send_all_users, name='pm_send_all_users'),
|
||||
]
|
||||
122
messagerie/views.py
Normal file
122
messagerie/views.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import PrivateMessage, PrivateMessageSubject
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from .forms import PrivateMessageForm, PrivateMessageSubjectForm
|
||||
from django.contrib import messages
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
private_messages = PrivateMessageSubject.objects.filter(
|
||||
(models.Q(receiver=request.user) | models.Q(sender=request.user)) & models.Q(is_active=True)
|
||||
).annotate(last_message_date=Max('messages__date_sent')).order_by('-last_message_date')
|
||||
|
||||
return render(request, 'messagerie/home.html', {'private_messages': private_messages})
|
||||
|
||||
@login_required
|
||||
def view_message(request, message_id):
|
||||
pm_message = PrivateMessageSubject.objects.get(pk=message_id)
|
||||
if (pm_message.receiver != request.user and pm_message.sender != request.user) or not pm_message.is_active:
|
||||
return redirect('pm_home')
|
||||
|
||||
private_messages = PrivateMessage.objects.filter(subject=pm_message).order_by('date_sent')
|
||||
|
||||
form = PrivateMessageForm(request.POST or None)
|
||||
|
||||
if not pm_message.is_read and pm_message.messages.first().author != request.user:
|
||||
pm_message.is_read = True
|
||||
pm_message.save()
|
||||
|
||||
if form.is_valid():
|
||||
message = form.save(commit=False)
|
||||
message.author = request.user
|
||||
message.subject = pm_message
|
||||
message.save()
|
||||
|
||||
pm_message.is_read = False
|
||||
pm_message.save()
|
||||
|
||||
form = PrivateMessageForm()
|
||||
|
||||
return render(request, 'messagerie/view_message.html', {'pm_message': pm_message, 'private_messages': private_messages, 'form': form})
|
||||
|
||||
return render(request, 'messagerie/view_message.html', {'pm_message': pm_message, 'private_messages': private_messages, 'form': form})
|
||||
|
||||
@login_required
|
||||
def new_message(request):
|
||||
form_subject = PrivateMessageSubjectForm(request.POST or None)
|
||||
form_message = PrivateMessageForm(request.POST or None)
|
||||
|
||||
if form_subject.is_valid() and form_message.is_valid():
|
||||
if form_subject.cleaned_data['receiver'] == request.user:
|
||||
messages.error(request, 'Vous ne pouvez pas vous envoyer un message à vous-même')
|
||||
return redirect('pm_home')
|
||||
subject = form_subject.save(commit=False)
|
||||
subject.sender = request.user
|
||||
subject.is_read = False
|
||||
subject.save()
|
||||
|
||||
message = form_message.save(commit=False)
|
||||
message.author = request.user
|
||||
message.subject = subject
|
||||
message.save()
|
||||
|
||||
return redirect('pm_view', message_id=message.id)
|
||||
|
||||
return render(request, 'messagerie/new_message.html', {'form_subject': form_subject, 'form_message': form_message})
|
||||
|
||||
def delete_subject(request, message_id):
|
||||
pm_message = PrivateMessageSubject.objects.get(pk=message_id)
|
||||
if pm_message.receiver != request.user and pm_message.sender != request.user:
|
||||
return redirect('pm_home')
|
||||
|
||||
pm_message.is_active = False
|
||||
pm_message.is_read = True
|
||||
pm_message.save()
|
||||
|
||||
messages.success(request, 'Le sujet a bien été supprimé')
|
||||
|
||||
return redirect('pm_home')
|
||||
|
||||
@login_required
|
||||
def send_all_users(request):
|
||||
from users.models import User
|
||||
all_users = User.objects.all()
|
||||
sender = User.objects.get(username='RetroBot')
|
||||
for user in all_users:
|
||||
subject = PrivateMessageSubject.objects.create(
|
||||
receiver=user,
|
||||
sender=User.objects.get(username='RetroBot'),
|
||||
subject="Bienvenue sur PassionRetro !"
|
||||
)
|
||||
|
||||
PrivateMessage.objects.create(
|
||||
subject=subject,
|
||||
author=User.objects.get(username='RetroBot'),
|
||||
message=f"""[b]Bienvenue sur Passion Retro ![/b]
|
||||
|
||||
Salut [b]{user.username}[/b] !,
|
||||
|
||||
Merci de nous avoir rejoints dans cette aventure dédiée aux passionnés de rétro ! Que tu sois fan de consoles vintage, collectionneur d'objets d'époque ou simple curieux, tu es ici chez toi.
|
||||
|
||||
✨ [b]Découvre tout ce que Passion Retro a à offrir :[/b]
|
||||
Plonge dans nos articles pour en apprendre plus sur les trésors du passé, participe aux discussions sur le forum et teste tes connaissances avec nos jeux rétro. Chaque section est là pour te permettre de partager ta passion et d'en apprendre davantage.
|
||||
|
||||
🚀 [b]Rejoins la communauté :[/b]
|
||||
Ton avis et tes contributions sont précieux ! N’hésite pas à lancer une discussion sur le forum, à réagir aux articles ou à défier les autres membres sur nos jeux. Plus nous sommes actifs, plus l’expérience sera enrichissante pour tous.
|
||||
|
||||
Si tu as des questions ou des suggestions pour améliorer le site, contacte-nous. Nous sommes là pour t'accompagner !
|
||||
|
||||
Encore une fois, bienvenue parmi nous et prépare-toi à replonger dans l’univers du rétro !
|
||||
|
||||
À bientôt,
|
||||
[b]L'équipe Passion Retro[/b]""")
|
||||
|
||||
subject = PrivateMessageSubject.objects.create(receiver=user, sender=sender, subject='Bienvenue sur la messagerie privée')
|
||||
PrivateMessage.objects.create(
|
||||
subject=subject,
|
||||
author=sender,
|
||||
message=f"Cher [b]{user.username}[/b],\n\nBienvenue sur la messagerie privée de passion-retro. Tu peux désormais échanger des messages privés avec les autres membres du site.\n\nQuant a moi, je suis un nouvel utilisateur du site venu de la planète [b]Retronia[/b] ! J'ai été investis de la mission la plus importante de ma longue vie : [u]être le meilleur assistant de passion-retro[/u] ! J'espère sincèrement y parvenir !\n\nJe suis encore en orbite actuellement, mais guette les infos car je serait bientôt présent !\n\nCordialement,\n{sender.username}"
|
||||
)
|
||||
return redirect('pm_home')
|
||||
0
passion_retro/__init__.py
Executable file
0
passion_retro/__init__.py
Executable file
16
passion_retro/asgi.py
Executable file
16
passion_retro/asgi.py
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for passion_retro project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passion_retro.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
199
passion_retro/settings.py
Executable file
199
passion_retro/settings.py
Executable file
|
|
@ -0,0 +1,199 @@
|
|||
"""
|
||||
Django settings for passion_retro project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.16.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import json
|
||||
|
||||
load_dotenv()
|
||||
|
||||
if os.getenv('DATABASE_ENGINE') == "django.db.backends.mysql":
|
||||
print("c'est mariaDB")
|
||||
import pymysql
|
||||
pymysql.install_as_MySQLdb()
|
||||
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||
GOOGLE_PUBLIC_KEY = os.getenv('GOOGLE_PUBLIC_KEY')
|
||||
GOOGLE_PRIVATE_KEY = os.getenv('GOOGLE_PRIVATE_KEY')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv('DEBUG') == 'True'
|
||||
|
||||
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(',')
|
||||
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(',')
|
||||
USE_X_FORWARDED_HOST = os.getenv('USE_X_FORWARDED_HOST') == 'True'
|
||||
SECURE_PROXY_SSL_HEADER = tuple(os.getenv('SECURE_PROXY_SSL_HEADER').split(','))
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# "django.contrib.sites",
|
||||
|
||||
"maintenance",
|
||||
"home",
|
||||
"posts",
|
||||
"users",
|
||||
"gallery",
|
||||
"forum",
|
||||
"tchat",
|
||||
"guestbook",
|
||||
"games",
|
||||
"quiz",
|
||||
"messagerie",
|
||||
"shop",
|
||||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"users.middleware.UserStatsMiddleware",
|
||||
"forum.middleware.ForumStatsMiddleware",
|
||||
"guestbook.middleware.GuestbookMiddleware",
|
||||
"posts.middleware.PostsMiddleware",
|
||||
'maintenance.middleware.MaintenanceMiddleware',
|
||||
"users.middleware.UserLevelMiddleware",
|
||||
"users.middleware.UserLevelUpMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "passion_retro.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": (BASE_DIR, 'templates'),
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"posts.context_processors.pending_posts_count",
|
||||
"quiz.context_processors.pending_quizes_count",
|
||||
"messagerie.context_processors.pending_pm_count",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "passion_retro.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': os.getenv('DATABASE_ENGINE'), # Utilise MySQL/MariaDB
|
||||
'NAME': os.getenv('DATABASE_NAME') if os.getenv('DATABASE_ENGINE') == "django.db.backends.mysql" else BASE_DIR / os.getenv('DATABASE_NAME'),
|
||||
'USER': os.getenv('DATABASE_USER'),
|
||||
'PASSWORD': os.getenv('DATABASE_PASSWORD'),
|
||||
'HOST': os.getenv('DATABASE_HOST'),
|
||||
'PORT': os.getenv('DATABASE_PORT', '3306'), # Port par défaut de MySQL/MariaDB
|
||||
'OPTIONS': {
|
||||
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
|
||||
'charset': 'utf8mb4',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "fr-FR"
|
||||
|
||||
TIME_ZONE = "Europe/Paris"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
# URL pour accéder aux fichiers statiques
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# Chemin absolu vers le répertoire où collecter les fichiers statiques
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||
|
||||
# Répertoires supplémentaires pour rechercher les fichiers statiques
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'static'),
|
||||
]
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
LOGOUT_REDIRECT_URL = os.getenv('LOGOUT_REDIRECT_URL', default='/')
|
||||
LOGIN_URL = os.getenv('LOGIN_URL', default='/login/')
|
||||
|
||||
AUTH_USER_MODEL = os.getenv('AUTH_USER_MODEL', default='users.User')
|
||||
|
||||
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
|
||||
EMAIL_HOST = os.getenv('EMAIL_HOST', default='smtp.gmail.com')
|
||||
EMAIL_PORT = os.getenv('EMAIL_PORT', default=587)
|
||||
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', default=True)
|
||||
EMAIL_USE_SSL = os.getenv('EMAIL_USE_TLS', default=False)
|
||||
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', default='webmaster@localhost')
|
||||
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', default='password')
|
||||
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', default='webmaster@localhost')
|
||||
40
passion_retro/urls.py
Executable file
40
passion_retro/urls.py
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
URL configuration for passion_retro project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
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.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import include, path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("maintenance/", include("maintenance.urls")),
|
||||
path("", include("home.urls")),
|
||||
path("posts/", include("posts.urls")),
|
||||
path("forum/", include("forum.urls")),
|
||||
path("users/", include("users.urls")),
|
||||
path("guestbook/", include("guestbook.urls")),
|
||||
path("gallery/", include("gallery.urls")),
|
||||
path("games/", include("games.urls")),
|
||||
path("quiz/", include("quiz.urls")),
|
||||
path("pm/", include("messagerie.urls")),
|
||||
path("shop/", include("shop.urls")),
|
||||
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
]
|
||||
|
||||
handler404 = 'home.views.custom_404'
|
||||
4
passion_retro/views.py
Normal file
4
passion_retro/views.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
def custom_404(request, exception):
|
||||
return render(request, "errors/404.html", status=404)
|
||||
16
passion_retro/wsgi.py
Executable file
16
passion_retro/wsgi.py
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for passion_retro project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passion_retro.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
0
posts/__init__.py
Executable file
0
posts/__init__.py
Executable file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue