diff --git a/boards/abstracts/constants.py b/boards/abstracts/constants.py --- a/boards/abstracts/constants.py +++ b/boards/abstracts/constants.py @@ -1,1 +1,4 @@ +import re + FILE_DIRECTORY = 'files/' +REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE) diff --git a/boards/abstracts/sticker_factory.py b/boards/abstracts/sticker_factory.py --- a/boards/abstracts/sticker_factory.py +++ b/boards/abstracts/sticker_factory.py @@ -18,7 +18,8 @@ class SessionStickerFactory(StickerFacto class ModelStickerFactory(StickerFactory): def get_image(self, alias): - return Attachment.objects.get_by_alias(alias) + if alias.count('/') == 1: + return Attachment.objects.get_by_alias(alias) def get_attachment_by_alias(alias, session): diff --git a/boards/admin.py b/boards/admin.py --- a/boards/admin.py +++ b/boards/admin.py @@ -1,5 +1,6 @@ from boards.abstracts.sticker_factory import StickerFactory -from boards.models.attachment import FILE_TYPES_IMAGE, AttachmentSticker +from boards.models.attachment import FILE_TYPES_IMAGE, AttachmentSticker, \ + StickerPack from django.contrib import admin from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse @@ -166,6 +167,11 @@ class AttachmentStickerAdmin(admin.Model search_fields = ('name',) +@admin.register(StickerPack) +class StickerPackAdmin(admin.ModelAdmin): + search_fields = ('name',) + + @admin.register(GlobalId) class GlobalIdAdmin(admin.ModelAdmin): def is_linked(self, obj): diff --git a/boards/forms/__init__.py b/boards/forms/__init__.py --- a/boards/forms/__init__.py +++ b/boards/forms/__init__.py @@ -2,7 +2,6 @@ import hashlib import logging import re import time -import traceback import pytz @@ -18,12 +17,14 @@ from django.core.cache import cache import boards.settings as board_settings import neboard from boards import utils +from boards.abstracts.constants import REGEX_TAGS from boards.abstracts.sticker_factory import get_attachment_by_alias from boards.abstracts.settingsmanager import get_settings_manager from boards.forms.fields import UrlFileField from boards.mdx_neboard import formatters from boards.models import Attachment from boards.models import Tag +from boards.models.attachment import StickerPack from boards.models.attachment.downloaders import download, REGEX_MAGNET from boards.models.post import TITLE_MAX_LENGTH from boards.utils import validate_file_size, get_file_mimetype, \ @@ -36,7 +37,6 @@ SECTION_FORMS = 'Forms' POW_HASH_LENGTH = 16 POW_LIFE_MINUTES = 5 -REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE) REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE) REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE) @@ -485,6 +485,7 @@ class ThreadForm(PostForm): widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}), max_length=100, label=_('Tags'), required=True) monochrome = forms.BooleanField(label=_('Monochrome'), required=False) + stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False) def clean_tags(self): tags = self.cleaned_data['tags'].strip() @@ -534,6 +535,36 @@ class ThreadForm(PostForm): def is_monochrome(self): return self.cleaned_data['monochrome'] + def clean_stickerpack(self): + stickerpack = self.cleaned_data['stickerpack'] + if stickerpack: + tripcode = self.get_tripcode() + if not tripcode: + raise forms.ValidationError(_( + 'Tripcode should be specified to own a stickerpack.')) + title = self.get_title() + if not title: + raise forms.ValidationError(_( + 'Title should be specified as a stickerpack name.')) + if not REGEX_TAGS.match(title): + raise forms.ValidationError(_('Inappropriate sticker pack name.')) + + existing_pack = StickerPack.objects.filter(name=title).first() + if existing_pack: + if existing_pack.tripcode != tripcode: + raise forms.ValidationError(_( + 'A sticker pack with this name already exists and is' + ' owned by another tripcode.')) + if not existing_pack.tripcode: + raise forms.ValidationError(_( + 'This sticker pack can only be updated by an ' + 'administrator.')) + + return stickerpack + + def is_stickerpack(self): + return self.cleaned_data['stickerpack'] + class SettingsForm(NeboardForm): diff --git a/boards/locale/ru/LC_MESSAGES/django.mo b/boards/locale/ru/LC_MESSAGES/django.mo index 05b7597076740e7609027d1302020687531bc18f..b9bac8aed719fc3e40e4de0d114830a3d0a0e612 GIT binary patch literal 14069 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 diff --git a/boards/locale/uk/LC_MESSAGES/django.po b/boards/locale/uk/LC_MESSAGES/django.po --- a/boards/locale/uk/LC_MESSAGES/django.po +++ b/boards/locale/uk/LC_MESSAGES/django.po @@ -619,3 +619,24 @@ msgstr "Глобальні стікери" msgid "Remove sticker" msgstr "Видалити стікер" + +msgid "Sticker Pack" +msgstr "Набір Стікерів" + +msgid "Tripcode should be specified to own a stickerpack." +msgstr "Для володіння набором стікерів необхідно вказати тріпкод." + +msgid "Title should be specified as a stickerpack name." +msgstr "Заголовок повинен бути вказаний в якості імені набору стікерів." + +msgid "A sticker pack with this name already exists and is owned by another tripcode." +msgstr "Набір стікерів з вказаним іменем вже є в наявності та належить іншому тріпкоду." + +msgid "This sticker pack can only be updated by an administrator." +msgstr "Цей набір стікерів може бути змінений лише адміністратором." + +msgid "To add a sticker, create a stickerpack thread using the title as a pack name, and a tripcode to own the pack. Then, add posts with title as a sticker name, and the same tripcode, to the thread. Their attachments would become stickers." +msgstr "Щоб додати стікер, створіть тему-набір з заголовком у якості набору стікерів, та тріпкодом для підтвердження володіння наборомт. Потім, додавайте повідомлення з заголовком у якості імені стікеру та з тим самим тріпкодом. Їхні вкладення станут стікерами." + +msgid "Inappropriate sticker pack name." +msgstr "Неприпустиме ім'я набору стікерів." diff --git a/boards/migrations/0066_auto_20171025_1148.py b/boards/migrations/0066_auto_20171025_1148.py new file mode 100644 --- /dev/null +++ b/boards/migrations/0066_auto_20171025_1148.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-10-25 08:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('boards', '0065_attachmentsticker'), + ] + + def stickers_to_pack(apps, schema_editor): + AttachmentSticker = apps.get_model('boards', 'AttachmentSticker') + StickerPack = apps.get_model('boards', 'StickerPack') + + stickers = AttachmentSticker.objects.all() + if len(stickers) > 0: + stickerpack = StickerPack.objects.create(name='general') + for sticker in stickers: + sticker.stickerpack = stickerpack + if '/' in sticker.name: + sticker.name = sticker.name.split('/')[1] + sticker.save() + + operations = [ + migrations.CreateModel( + name='StickerPack', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(unique=True)), + ('tripcode', models.TextField(blank=True)), + ], + ), + migrations.AddField( + model_name='attachmentsticker', + name='stickerpack', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boards.StickerPack', null=True), + preserve_default=False, + ), + migrations.AddField( + model_name='thread', + name='stickerpack', + field=models.BooleanField(default=False), + ), + migrations.RunPython(stickers_to_pack), + migrations.AlterField( + model_name='attachmentsticker', + name='stickerpack', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boards.StickerPack', null=False), + ), + ] diff --git a/boards/models/attachment/__init__.py b/boards/models/attachment/__init__.py --- a/boards/models/attachment/__init__.py +++ b/boards/models/attachment/__init__.py @@ -55,8 +55,9 @@ class AttachmentManager(models.Manager): return attachment def get_by_alias(self, name): + pack_name, sticker_name = name.split('/') try: - return AttachmentSticker.objects.get(name=name).attachment + return AttachmentSticker.objects.get(name=sticker_name, stickerpack__name=pack_name).attachment except AttachmentSticker.DoesNotExist: return None @@ -163,9 +164,22 @@ class Attachment(models.Model): return self.url is None or len(self.url) == 0 +class StickerPack(models.Model): + name = models.TextField(unique=True) + tripcode = models.TextField(blank=True) + + def __str__(self): + return self.name + + class AttachmentSticker(models.Model): attachment = models.ForeignKey('Attachment') name = models.TextField(unique=True) + stickerpack = models.ForeignKey('StickerPack') def __str__(self): - return self.name + # Local stickers do not have a sticker pack + if hasattr(self, 'stickerpack'): + return '{}/{}'.format(str(self.stickerpack), self.name) + else: + return self.name diff --git a/boards/models/post/manager.py b/boards/models/post/manager.py --- a/boards/models/post/manager.py +++ b/boards/models/post/manager.py @@ -1,20 +1,20 @@ import logging - from datetime import datetime, timedelta, date from datetime import time as dtime -from boards.abstracts.exceptions import BannedException, ArchiveException +from django.core.exceptions import PermissionDenied from django.db import models, transaction +from django.dispatch import Signal from django.utils import timezone -from django.dispatch import Signal -from django.core.exceptions import PermissionDenied import boards - -from boards.models.user import Ban +from boards import utils +from boards.abstracts.exceptions import ArchiveException +from boards.abstracts.constants import REGEX_TAGS from boards.mdx_neboard import Parser from boards.models import Attachment -from boards import utils +from boards.models.attachment import StickerPack, AttachmentSticker +from boards.models.user import Ban __author__ = 'neko259' @@ -30,7 +30,7 @@ class PostManager(models.Manager): def create_post(self, title: str, text: str, files=[], thread=None, ip=NO_IP, tags: list=None, tripcode='', monochrome=False, images=[], - file_urls=[]): + file_urls=[], stickerpack=False): """ Creates new post """ @@ -54,7 +54,7 @@ class PostManager(models.Manager): if not thread: thread = boards.models.thread.Thread.objects.create( bump_time=posting_time, last_edit_time=posting_time, - monochrome=monochrome) + monochrome=monochrome, stickerpack=stickerpack) list(map(thread.tags.add, tags)) new_thread = True @@ -89,6 +89,8 @@ class PostManager(models.Manager): thread.bump() thread.save() + self._create_stickers(post) + return post def delete_posts_by_ip(self, ip): @@ -190,3 +192,37 @@ class PostManager(models.Manager): def _add_file_to_post(self, file, post): post.attachments.add(Attachment.objects.create_with_hash(file)) + + def _create_stickers(self, post): + thread = post.get_thread() + stickerpack_thread = thread.is_stickerpack() + if stickerpack_thread: + logger = logging.getLogger('boards.stickers') + if not post.is_opening(): + has_title = len(post.title) > 0 + has_one_attachment = post.attachments.count() == 1 + opening_post = thread.get_opening_post() + valid_name = REGEX_TAGS.match(post.title) + if has_title and has_one_attachment and valid_name: + existing_sticker = AttachmentSticker.objects.filter( + name=post.get_title()).first() + attachment = post.attachments.first() + if existing_sticker: + existing_sticker.attachment = attachment + existing_sticker.save() + logger.info('Updated sticker {} with new attachment'.format(existing_sticker)) + else: + try: + stickerpack = StickerPack.objects.get( + name=opening_post.get_title(), tripcode=post.tripcode) + sticker = AttachmentSticker.objects.create( + stickerpack=stickerpack, name=post.get_title(), + attachment=attachment) + logger.info('Created sticker {}'.format(sticker)) + except StickerPack.DoesNotExist: + pass + else: + stickerpack, created = StickerPack.objects.get_or_create( + name=post.get_title(), tripcode=post.tripcode) + if created: + logger.info('Created stickerpack {}'.format(stickerpack)) diff --git a/boards/models/thread.py b/boards/models/thread.py --- a/boards/models/thread.py +++ b/boards/models/thread.py @@ -97,6 +97,7 @@ class Thread(models.Model): status = models.CharField(max_length=50, default=STATUS_ACTIVE, choices=STATUS_CHOICES, db_index=True) monochrome = models.BooleanField(default=False) + stickerpack = models.BooleanField(default=False) def get_tags(self) -> QuerySet: """ @@ -262,6 +263,9 @@ class Thread(models.Model): def is_monochrome(self): return self.monochrome + def is_stickerpack(self): + return self.stickerpack + # If tags have parent, add them to the tag list @transaction.atomic def refresh_tags(self): diff --git a/boards/signals.py b/boards/signals.py --- a/boards/signals.py +++ b/boards/signals.py @@ -1,5 +1,6 @@ import re import os +import logging from django.db.models.signals import post_save, pre_save, pre_delete, \ post_delete @@ -9,7 +10,8 @@ from django.utils import timezone from boards import thumbs from boards.mdx_neboard import get_parser -from boards.models import Post, GlobalId, Attachment +from boards.models import Post, GlobalId, Attachment, Thread +from boards.models.attachment import StickerPack, AttachmentSticker from boards.models.attachment.viewers import FILE_TYPES_IMAGE from boards.models.post import REGEX_NOTIFICATION, REGEX_REPLY,\ REGEX_GLOBAL_REPLY diff --git a/boards/templates/boards/aliases.html b/boards/templates/boards/aliases.html --- a/boards/templates/boards/aliases.html +++ b/boards/templates/boards/aliases.html @@ -15,7 +15,7 @@ {% for sticker in local_stickers %} {% endfor %} @@ -25,7 +25,7 @@ {% for sticker in global_stickers %} {% endfor %} {% endif %} diff --git a/boards/templates/boards/post.html b/boards/templates/boards/post.html --- a/boards/templates/boards/post.html +++ b/boards/templates/boards/post.html @@ -6,6 +6,9 @@