{{ media.caption }}
{% if user.is_authenticated and perms.blog.delete_photo %} {% endif %}pax_global_header 0000666 0000000 0000000 00000000064 14342105641 0014512 g ustar 00root root 0000000 0000000 52 comment=bb23fe58c11712114a7eef58675c58d3c7154d14 fotoblog-main/ 0000775 0000000 0000000 00000000000 14342105641 0013613 5 ustar 00root root 0000000 0000000 fotoblog-main/.gitignore 0000664 0000000 0000000 00000000105 14342105641 0015577 0 ustar 00root root 0000000 0000000 venv/ *.sqlite3 *__pycache__* .vscode/ fotoblog/.env fotoblog/media/ fotoblog-main/README.md 0000664 0000000 0000000 00000014131 14342105641 0015072 0 ustar 00root root 0000000 0000000 # fotoblog ## Getting started To make it easy for you to get started with GitLab, here's a list of recommended next steps. Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! ## Add your files - [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files - [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: ``` cd existing_repo git remote add origin https://gitlab.anthony-jacob.com/anthony.jacob/fotoblog.git git branch -M main git push -uf origin main ``` ## Integrate with your tools - [ ] [Set up project integrations](https://gitlab.anthony-jacob.com/anthony.jacob/fotoblog/-/settings/integrations) ## Collaborate with your team - [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) - [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) - [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) - [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) - [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) ## Test and Deploy Use the built-in continuous integration in GitLab. - [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) - [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) - [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) - [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) *** # Editing this README When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. ## Suggestions for a good README Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. ## Name Choose a self-explaining name for your project. ## Description Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. ## Badges On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. ## Visuals Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. ## Installation Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. ## Usage Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. ## Support Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. ## Roadmap If you have ideas for releases in the future, it is a good idea to list them in the README. ## Contributing State if you are open to contributions and what your requirements are for accepting them. For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. ## Authors and acknowledgment Show your appreciation to those who have contributed to the project. ## License For open source projects, say how it is licensed. ## Project status If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. fotoblog-main/fotoblog/ 0000775 0000000 0000000 00000000000 14342105641 0015426 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/ 0000775 0000000 0000000 00000000000 14342105641 0020445 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/__init__.py 0000664 0000000 0000000 00000000000 14342105641 0022544 0 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/admin.py 0000664 0000000 0000000 00000000235 14342105641 0022107 0 ustar 00root root 0000000 0000000 from django.contrib import admin from authentication.models import User class UserAdmin(admin.ModelAdmin): pass admin.site.register(User, UserAdmin) fotoblog-main/fotoblog/authentication/apps.py 0000664 0000000 0000000 00000000240 14342105641 0021756 0 ustar 00root root 0000000 0000000 from django.apps import AppConfig class AuthenticationConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "authentication" fotoblog-main/fotoblog/authentication/forms.py 0000664 0000000 0000000 00000001217 14342105641 0022146 0 ustar 00root root 0000000 0000000 from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import get_user_model from .models import User class LoginForm(forms.Form): username = forms.CharField(max_length=63, label='Nom d’utilisateur') password = forms.CharField(max_length=63, widget=forms.PasswordInput, label='Mot de passe') class SignupForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = get_user_model() fields = ('username', 'email', 'first_name', 'last_name', 'role') class ProfilePictureForm(forms.ModelForm): class Meta: model = User fields = ['profile_photo'] fotoblog-main/fotoblog/authentication/migrations/ 0000775 0000000 0000000 00000000000 14342105641 0022621 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/migrations/0001_initial.py 0000664 0000000 0000000 00000011566 14342105641 0025275 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-11-14 12:15 import django.contrib.auth.models import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): initial = True dependencies = [ ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( name="User", fields=[ ( "id", models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("password", models.CharField(max_length=128, verbose_name="password")), ( "last_login", models.DateTimeField( blank=True, null=True, verbose_name="last login" ), ), ( "is_superuser", models.BooleanField( default=False, help_text="Designates that this user has all permissions without explicitly assigning them.", verbose_name="superuser status", ), ), ( "username", models.CharField( error_messages={ "unique": "A user with that username already exists." }, help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", max_length=150, unique=True, validators=[ django.contrib.auth.validators.UnicodeUsernameValidator() ], verbose_name="username", ), ), ( "first_name", models.CharField( blank=True, max_length=150, verbose_name="first name" ), ), ( "last_name", models.CharField( blank=True, max_length=150, verbose_name="last name" ), ), ( "email", models.EmailField( blank=True, max_length=254, verbose_name="email address" ), ), ( "is_staff", models.BooleanField( default=False, help_text="Designates whether the user can log into this admin site.", verbose_name="staff status", ), ), ( "is_active", models.BooleanField( default=True, help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", verbose_name="active", ), ), ( "date_joined", models.DateTimeField( default=django.utils.timezone.now, verbose_name="date joined" ), ), ( "profil_photo", models.ImageField(upload_to="", verbose_name="Photo de profil"), ), ( "groups", models.ManyToManyField( blank=True, help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", related_name="user_set", related_query_name="user", to="auth.group", verbose_name="groups", ), ), ( "user_permissions", models.ManyToManyField( blank=True, help_text="Specific permissions for this user.", related_name="user_set", related_query_name="user", to="auth.permission", verbose_name="user permissions", ), ), ], options={ "verbose_name": "user", "verbose_name_plural": "users", "abstract": False, }, managers=[ ("objects", django.contrib.auth.models.UserManager()), ], ), ] 0002_remove_user_profil_photo_user_profile_photo_and_more.py 0000664 0000000 0000000 00000001735 14342105641 0036535 0 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/migrations # Generated by Django 4.1.3 on 2022-11-14 12:26 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("authentication", "0001_initial"), ] operations = [ migrations.RemoveField( model_name="user", name="profil_photo", ), migrations.AddField( model_name="user", name="profile_photo", field=models.ImageField( blank=True, default=None, null=True, upload_to="", verbose_name="Photo de profil", ), ), migrations.AddField( model_name="user", name="role", field=models.CharField( choices=[("CREATOR", "Créateur"), ("SUBSCRIBER", "Abonné")], default="SUBSCRIBER", max_length=30, verbose_name="Rôle", ), ), ] fotoblog-main/fotoblog/authentication/migrations/0003_auto_20221115_1802.py 0000664 0000000 0000000 00000002433 14342105641 0026236 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-11-15 17:02 from django.db import migrations from django import apps def create_groups(apps, schema_migration): User = apps.get_model('authentication', 'User') Group = apps.get_model('auth', 'Group') Permission = apps.get_model('auth', 'Permission') add_photo = Permission.objects.get(codename='add_photo') change_photo = Permission.objects.get(codename='change_photo') delete_photo = Permission.objects.get(codename='delete_photo') view_photo = Permission.objects.get(codename='view_photo') creator_permissions = [ add_photo, change_photo, delete_photo, view_photo, ] creators = Group(name='creators') creators.save() creators.permissions.set(creator_permissions) subscribers = Group(name='subscribers') subscribers.save() subscribers.permissions.add(view_photo) for user in User.objects.all(): if user.role == 'CREATOR': creators.user_set.add(user) if user.role == 'SUBSCRIBER': subscribers.user_set.add(user) class Migration(migrations.Migration): dependencies = [ ("authentication", "0002_remove_user_profil_photo_user_profile_photo_and_more"), ] operations = [migrations.RunPython(create_groups)] fotoblog-main/fotoblog/authentication/migrations/__init__.py 0000664 0000000 0000000 00000000000 14342105641 0024720 0 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/models.py 0000664 0000000 0000000 00000001777 14342105641 0022316 0 ustar 00root root 0000000 0000000 from django.db import models from django.contrib.auth.models import AbstractUser, Group class User(AbstractUser): CREATOR = 'CREATOR' SUBSCRIBER = 'SUBSCRIBER' ROLE_CHOICES = ( (CREATOR, 'Créateur'), (SUBSCRIBER, 'Abonné'), ) profile_photo = models.ImageField(verbose_name='Photo de profil', null=True, blank=True, default=None) role = models.CharField(max_length=30, choices=ROLE_CHOICES, verbose_name='Rôle', default='SUBSCRIBER') def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.role == self.CREATOR: group = Group.objects.get(name='creators') group.user_set.add(self) elif self.role == self.SUBSCRIBER: group = Group.objects.get(name='subscribers') group.user_set.add(self) fotoblog-main/fotoblog/authentication/templates/ 0000775 0000000 0000000 00000000000 14342105641 0022443 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/templates/authentication/ 0000775 0000000 0000000 00000000000 14342105641 0025462 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/authentication/templates/authentication/login.html 0000664 0000000 0000000 00000001012 14342105641 0027452 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - login{% endblock %} {% block content %}
{% include "messages_notif.html" %}
Pas encore membre ? Inscrivez-vous maintenant !
{% endblock %} fotoblog-main/fotoblog/authentication/templates/authentication/logout.html 0000664 0000000 0000000 00000000443 14342105641 0027662 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - {{ title }}{% endblock %} {% block content %}vous êtes bien déconnecté du site {{ site_name }} {% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/authentication/templates/authentication/profile_upload.html 0000664 0000000 0000000 00000000706 14342105641 0031357 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Photo de profil{% endblock %} {% block content %}{% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/authentication/templates/authentication/signup.html 0000664 0000000 0000000 00000001022 14342105641 0027650 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Inscription{% endblock %} {% block content %}{% include "messages_notif.html" %} {% if form.errors %} {{ form.errors }} {% endif %}
{% endblock %} fotoblog-main/fotoblog/authentication/templates/authentication/update_password.html 0000664 0000000 0000000 00000000736 14342105641 0031562 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Changement de mot de passe{% endblock %} {% block content %}{% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/authentication/templates/authentication/update_password_done.html 0000664 0000000 0000000 00000000423 14342105641 0032560 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Changement de mot de passe effectué{% endblock %} {% block content %}vous venez de changer votre mot de passe
{% endblock %} fotoblog-main/fotoblog/authentication/tests.py 0000664 0000000 0000000 00000000074 14342105641 0022162 0 ustar 00root root 0000000 0000000 from django.test import TestCase # Create your tests here. fotoblog-main/fotoblog/authentication/validators.py 0000664 0000000 0000000 00000001474 14342105641 0023175 0 ustar 00root root 0000000 0000000 from django.core.exceptions import ValidationError class ContainsLetterValidator: def validate(self, password, user=None): if not any(char.isalpha() for char in password): raise ValidationError( 'Le mot de passe doit contenir une lettre', code='password_no_letters' ) def get_help_text(self): return 'Votre mot de passe doit contenir au moins une lettre majuscule ou minuscule.' class ContainsNumberValidator: def validate(self, password, user=None): if not any(char.isdigit() for char in password): raise ValidationError( 'Le mot de passe doit contenir au moins un chiffre', code='password_no_digits' ) def get_help_text(self): return 'Votre mot de passe doit contenir au moins un chiffre.' fotoblog-main/fotoblog/authentication/views.py 0000664 0000000 0000000 00000005603 14342105641 0022160 0 ustar 00root root 0000000 0000000 from django.shortcuts import render, redirect from django.conf import settings from django.contrib import messages from django.views.generic import View from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth import authenticate, login, logout from .forms import LoginForm, ProfilePictureForm, SignupForm class LoginView(View): context = dict() template_name = 'authentication/login.html' form_class = LoginForm def get(self, request): self.context['form'] = self.form_class() return render(request, self.template_name, self.context) def post(self, request): self.context['form'] = form = self.form_class(request.POST) if form.is_valid(): user = authenticate( username=form.cleaned_data['username'], password=form.cleaned_data['password'], ) if user is not None: login(request, user) messages.add_message(request, messages.INFO, f'Bonjour, {user.username}! Vous êtes connecté.') return redirect('home') else: messages.add_message(request, messages.ERROR, 'Identifiants invalides.') return render(request, self.template_name, self.context) def logout_page(request): logout(request) return redirect('login') class SignupView(View): context = dict() template_name = 'authentication/signup.html' form_class = SignupForm def get(self, request): self.context['form'] = self.form_class() return render(request, self.template_name, self.context) def post(self, request): self.context['form'] = form = self.form_class(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect(settings.LOGIN_REDIRECT_URL) return render(request, self.template_name, self.context) class ProfilePictureView(LoginRequiredMixin, View): context = dict() template_name = 'authentication/profile_upload.html' form_class = ProfilePictureForm def get(self, request): self.context['form'] = self.form_class() return render(request, self.template_name, self.context) def post(self, request): self.context['form'] = form = self.form_class(request.POST, request.FILES, instance=request.user) if form.is_valid(): form.save() messages.add_message(request, messages.INFO, 'photo ajoutée avec succès') return redirect('home') return render(request, self.template_name, self.context) fotoblog-main/fotoblog/blog/ 0000775 0000000 0000000 00000000000 14342105641 0016351 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/__init__.py 0000664 0000000 0000000 00000000000 14342105641 0020450 0 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/admin.py 0000664 0000000 0000000 00000000077 14342105641 0020017 0 ustar 00root root 0000000 0000000 from django.contrib import admin # Register your models here. fotoblog-main/fotoblog/blog/apps.py 0000664 0000000 0000000 00000000214 14342105641 0017663 0 ustar 00root root 0000000 0000000 from django.apps import AppConfig class BlogConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "blog" fotoblog-main/fotoblog/blog/forms.py 0000664 0000000 0000000 00000001430 14342105641 0020047 0 ustar 00root root 0000000 0000000 from django import forms from .models import Photo, Blog class MediaForm(forms.ModelForm): class Meta: model = Photo fields = ['image', 'caption'] class MediaDeleteForm(forms.Form): media_delete = forms.BooleanField(widget=forms.HiddenInput, initial=True) class BlogForm(forms.ModelForm): post_edit = forms.BooleanField(widget=forms.HiddenInput, initial=True) class Meta: model = Blog fields = ['title', 'content'] widgets = { 'content': forms.Textarea(attrs={'cols': 80, 'rows': 20}), } class BlogDeleteForm(forms.Form): post_delete = forms.BooleanField(widget=forms.HiddenInput, initial=True) fotoblog-main/fotoblog/blog/migrations/ 0000775 0000000 0000000 00000000000 14342105641 0020525 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/migrations/0001_initial.py 0000664 0000000 0000000 00000004574 14342105641 0023202 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-11-14 16:43 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="Photo", fields=[ ( "id", models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("image", models.ImageField(upload_to="")), ("caption", models.CharField(blank=True, max_length=128)), ("date_created", models.DateTimeField(auto_now_add=True)), ( "uploader", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, ), ), ], ), migrations.CreateModel( name="Blog", fields=[ ( "id", models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("title", models.CharField(max_length=128)), ("content", models.CharField(max_length=5000)), ("date_created", models.DateTimeField(auto_now_add=True)), ("starred", models.BooleanField(default=False)), ( "Photo", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="blog.photo", ), ), ( "author", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, ), ), ], ), ] fotoblog-main/fotoblog/blog/migrations/0002_rename_photo_blog_photo.py 0000664 0000000 0000000 00000000531 14342105641 0026433 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-11-15 09:01 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("blog", "0001_initial"), ] operations = [ migrations.RenameField( model_name="blog", old_name="Photo", new_name="photo", ), ] fotoblog-main/fotoblog/blog/migrations/0003_blog_word_count.py 0000664 0000000 0000000 00000000771 14342105641 0024734 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-11-15 12:15 import django.core.validators from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("blog", "0002_rename_photo_blog_photo"), ] operations = [ migrations.AddField( model_name="blog", name="word_count", field=models.IntegerField( default=0, validators=[django.core.validators.MinValueValidator(0)] ), ), ] fotoblog-main/fotoblog/blog/migrations/0004_alter_blog_options.py 0000664 0000000 0000000 00000000726 14342105641 0025434 0 ustar 00root root 0000000 0000000 # Generated by Django 4.1.3 on 2022-12-01 10:45 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("blog", "0003_blog_word_count"), ] operations = [ migrations.AlterModelOptions( name="blog", options={ "permissions": [ ("change_blog_title", "Peut changer le titre d’un billet de blog") ] }, ), ] fotoblog-main/fotoblog/blog/migrations/__init__.py 0000664 0000000 0000000 00000000000 14342105641 0022624 0 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/models.py 0000664 0000000 0000000 00000005671 14342105641 0020217 0 ustar 00root root 0000000 0000000 import os from django.db import models from django.conf import settings from django.core.validators import MinValueValidator from PIL import Image from django.dispatch import receiver class Photo(models.Model): image = models.ImageField() caption = models.CharField(max_length=128, blank=True) uploader = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True) IMAGE_MAX_SIZE = (800, 800) def resize_image(self): image = Image.open(self.image) image.thumbnail(self.IMAGE_MAX_SIZE) image.save(self.image.path) image.close() def save(self, *args, **kwargs): super().save(*args, **kwargs) self.resize_image() class Blog(models.Model): class Meta: permissions = [ ('change_blog_title', 'Peut changer le titre d’un billet de blog') ] photo = models.ForeignKey(Photo, null=True, on_delete=models.SET_NULL, blank=True) title = models.CharField(max_length=128) content = models.CharField(max_length=5000) word_count = models.IntegerField(validators=[MinValueValidator(0)], default=0) author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True) starred = models.BooleanField(default=False) def _word_count(self): words = self.content.split(" ") return len(words) def save(self, *args, **kwargs): self.word_count = self._word_count() super().save(*args, **kwargs) # These two auto-delete files from filesystem when they are unneeded: @receiver(models.signals.post_delete, sender=Photo) def auto_delete_file_on_delete(sender, instance, **kwargs): """ Deletes file from filesystem when corresponding `MediaFile` object is deleted. """ if instance.image: if os.path.isfile(instance.image.path): os.remove(instance.image.path) @receiver(models.signals.pre_save, sender=Photo) def auto_delete_file_on_change(sender, instance, **kwargs): """ Deletes old file from filesystem when corresponding `MediaFile` object is updated with new file. """ if not instance.pk: return False try: old_file = Photo.objects.get(pk=instance.pk).image except Photo.DoesNotExist: return False new_file = instance.image if not old_file == new_file: if old_file.name and os.path.isfile(old_file.path): # TODO: find why sometimes the file is used, not closed and trigger PermissionError # seems due to Photo save() overide since the problem does not occur when it's deleted try: os.remove(old_file.path) except PermissionError: return False fotoblog-main/fotoblog/blog/templates/ 0000775 0000000 0000000 00000000000 14342105641 0020347 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/templates/blog/ 0000775 0000000 0000000 00000000000 14342105641 0021272 5 ustar 00root root 0000000 0000000 fotoblog-main/fotoblog/blog/templates/blog/create_blog_post.html 0000664 0000000 0000000 00000002075 14342105641 0025477 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Création d'un billet de blog{% endblock %} {% block content %}{% endblock %} fotoblog-main/fotoblog/blog/templates/blog/home.html 0000664 0000000 0000000 00000001153 14342105641 0023110 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Home{% endblock %} {% block content %}
{% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/blog/templates/blog/media_delete.html 0000664 0000000 0000000 00000000767 14342105641 0024573 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Media Upload{% endblock %} {% block content %}{% include "messages_notif.html" %} êtes vous sûr de vouloir supprimer le media {{ media.caption }}
{% endblock %} fotoblog-main/fotoblog/blog/templates/blog/media_list.html 0000664 0000000 0000000 00000001364 14342105641 0024276 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Home{% endblock %} {% block content %}il n'y a pas de médias
{% else %} {% for media in medias %}{{ media.caption }}
{% if user.is_authenticated and perms.blog.delete_photo %} {% endif %}{% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/blog/templates/blog/media_view.html 0000664 0000000 0000000 00000000564 14342105641 0024276 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Media {{ media.id }} {% endblock %} {% block content %}{{ media.caption }}
{% include "messages_notif.html" %}
{% endblock %} fotoblog-main/fotoblog/blog/templates/blog/post_edit.html 0000664 0000000 0000000 00000001742 14342105641 0024156 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %} {% block title %}Fotoblog - Création d'un billet de blog{% endblock %} {% block content %}{% include "messages_notif.html" %}
il n'y a pas d'article
{% else %} {% for post in posts %}
{{ post.content }}
article créé par {{ post.author }} le {{ post.date_created }}
cet article fait {{ post.word_count }} mots
{% if user.is_authenticated and perms.blog.change_blog %} {% endif %}