Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/magplan/migrations/0010_auto_20210624_1041.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import django.db.models.manager
from django.db import migrations, models

import magplan.models
from magplan.utils import current_site_id


class Migration(migrations.Migration):

dependencies = [
("sites", "0002_alter_domain_unique"),
("magplan", "0009_auto_20210523_1641"),
Expand Down Expand Up @@ -40,7 +39,7 @@ class Migration(migrations.Migration):
model_name="idea",
name="site",
field=models.ForeignKey(
default=magplan.models.current_site_id,
default=current_site_id,
on_delete=django.db.models.deletion.CASCADE,
to="sites.site",
),
Expand All @@ -49,7 +48,7 @@ class Migration(migrations.Migration):
model_name="issue",
name="site",
field=models.ForeignKey(
default=magplan.models.current_site_id,
default=current_site_id,
on_delete=django.db.models.deletion.CASCADE,
to="sites.site",
),
Expand All @@ -58,7 +57,7 @@ class Migration(migrations.Migration):
model_name="post",
name="site",
field=models.ForeignKey(
default=magplan.models.current_site_id,
default=current_site_id,
on_delete=django.db.models.deletion.CASCADE,
to="sites.site",
),
Expand All @@ -77,7 +76,7 @@ class Migration(migrations.Migration):
model_name="section",
name="site",
field=models.ForeignKey(
default=magplan.models.current_site_id,
default=current_site_id,
on_delete=django.db.models.deletion.CASCADE,
to="sites.site",
),
Expand All @@ -86,7 +85,7 @@ class Migration(migrations.Migration):
model_name="stage",
name="site",
field=models.ForeignKey(
default=magplan.models.current_site_id,
default=current_site_id,
on_delete=django.db.models.deletion.CASCADE,
to="sites.site",
),
Expand Down
24 changes: 24 additions & 0 deletions src/magplan/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from .user import User, Profile
from .comment import Comment
from .idea import Idea
from .issue import Issue
from .magazine import Magazine
from .post import Post, Attachment
from .section import Section
from .site_preference_model import SitePreferenceModel
from .stage import Stage
from .vote import Vote


__all__ = [
"User",
"Post",
"Comment",
"Idea",
"Issue",
"Magazine",
"Section",
"SitePreferenceModel",
"Stage",
"Vote",
]
38 changes: 38 additions & 0 deletions src/magplan/models/abs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import typing as tp

from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
from django.db.models import QuerySet

from magplan.utils import current_site_id

class AbstractBase(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
_old_id = models.PositiveIntegerField(null=True, blank=True)

class Meta:
abstract = True


class AbstractSiteModel(models.Model):
"""
Support for multisite managers
"""

site = models.ForeignKey(
Site, on_delete=models.CASCADE, default=current_site_id
)
objects = models.Manager()
on_current_site = CurrentSiteManager()

class Meta:
abstract = True

@classmethod
def on_site(cls, site: tp.Optional[Site]) -> QuerySet:
if not site:
return cls.objects

return cls.objects.filter(site=site)
52 changes: 52 additions & 0 deletions src/magplan/models/comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import JSONField

from magplan.models.abs import AbstractBase
from magplan.models.user import User
from magplan.xmd import render_md


class Comment(AbstractBase):
SYSTEM_ACTION_SET_STAGE = 5
SYSTEM_ACTION_UPDATE = 10
SYSTEM_ACTION_CHANGE_META = 15
SYSTEM_ACTION_CHOICES = (
(SYSTEM_ACTION_SET_STAGE, "Set stage"),
(SYSTEM_ACTION_UPDATE, "Update"),
(SYSTEM_ACTION_CHANGE_META, "Change meta"),
)

TYPE_SYSTEM = 5
TYPE_PRIVATE = 10
TYPE_PUBLIC = 15
TYPE_CHOICES = (
(TYPE_SYSTEM, "system"),
(TYPE_PRIVATE, "private"),
(TYPE_PUBLIC, "public"),
)
text = models.TextField(blank=True)
type = models.SmallIntegerField(choices=TYPE_CHOICES, default=TYPE_PRIVATE)
user = models.ForeignKey(User, on_delete=models.CASCADE)

content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
commentable = GenericForeignKey("content_type", "object_id")
meta = JSONField(default=dict)

def __str__(self):
return "%s, %s:%s..." % (self.user_id, self.type, self.text[0:50])

@property
def html(self):
return render_md(self.text, render_lead=False)

@property
def changelog(self):
try:
md = "\n".join(self.meta["comment"]["changelog"])
except Exception:
md = ""

return render_md(md, render_lead=False)
117 changes: 117 additions & 0 deletions src/magplan/models/idea.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import os
import typing as tp

import html2text
from django.contrib.contenttypes.fields import GenericRelation
from django.core.mail import EmailMultiAlternatives
from django.db import models
from django.template.loader import render_to_string

from magplan.conf import settings as config
from magplan.models.abs import AbstractSiteModel, AbstractBase
from magplan.models.user import User
from magplan.xmd import render_md


NEW_IDEA_NOTIFICATION_PREFERENCE_NAME = "magplan__new_idea_notification"

class Idea(AbstractSiteModel, AbstractBase):
AUTHOR_TYPE_NO = "NO"
AUTHOR_TYPE_NEW = "NW"
AUTHOR_TYPE_EXISTING = "EX"
AUTHOR_TYPE_CHOICES = [
(AUTHOR_TYPE_NO, "Нет автора"),
(AUTHOR_TYPE_NEW, "Новый автор"),
(AUTHOR_TYPE_EXISTING, "Существующий автор(ы)"),
]
title = models.CharField(
null=False, blank=False, max_length=255, verbose_name="Заголовок"
)
description = models.TextField(verbose_name="Описание")
approved = models.BooleanField(null=True)
editor = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="editor"
)
post = models.OneToOneField(
"Post", on_delete=models.SET_NULL, null=True, blank=True
)
comments = GenericRelation("Comment")
author_type = models.CharField(
max_length=2,
choices=AUTHOR_TYPE_CHOICES,
default=AUTHOR_TYPE_NO,
verbose_name="Автор",
)
authors_new = models.CharField(
max_length=255, null=True, blank=True, verbose_name="Новые автор"
)
authors = models.ManyToManyField(
User, verbose_name="Авторы", related_name="authors", blank=True
)

def voted(self, user):
vote = next((v for v in self.votes.all() if v.user_id == user.id), None)

if vote:
return True
return False

def _send_vote_notification(self, recipient: User) -> None:
subject = f"Новая идея «{self.title}». Голосуйте!"

context = {"idea": self, "APP_URL": os.environ.get("APP_URL")}
message_html_content: str = render_to_string(
"email/new_idea.html", context
)
message_text_content: str = html2text.html2text(message_html_content)

msg = EmailMultiAlternatives(
subject,
message_text_content,
config.PLAN_EMAIL_FROM,
[recipient.email],
)
msg.attach_alternative(message_html_content, "text/html")
msg.send()

def send_vote_notifications(self) -> None:
active_users: tp.List[User] = User.objects.filter(
is_active=True
).exclude(id=self.editor_id)
recipients: tp.List[User] = [
user
for user in active_users
if user.preferences[NEW_IDEA_NOTIFICATION_PREFERENCE_NAME]
]

for recipient in recipients:
self._send_vote_notification(recipient)

def __str__(self):
return self.title

class Meta:
permissions = (
("edit_extended_idea_attrs", "Edit extended Idea attributes"),
("recieve_idea_email_updates", "Recieve email updates for Idea"),
)

@property
def comments_(self):
return self.comments.order_by("created_at").all

@property
def score(self):
MAX_SCORE = 100

all_scores = sum([v.score for v in self.votes.all()])
max_scores = len(self.votes.all()) * MAX_SCORE

return round(all_scores / max_scores * 100)

@property
def description_html(self):
return render_md(self.description)


NEW_IDEA_NOTIFICATION_PREFERENCE_NAME = "magplan__new_idea_notification"
26 changes: 26 additions & 0 deletions src/magplan/models/issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import datetime

from django.db import models

from magplan.models.abs import AbstractSiteModel, AbstractBase
from magplan.models.magazine import Magazine


class Issue(AbstractSiteModel, AbstractBase):
class Meta:
ordering = ["-number"]

def __str__(self):
return "%s #%s" % (self.magazine, self.number)

number = models.SmallIntegerField(null=False, blank=False, default=0)
title = models.CharField(null=True, blank=False, max_length=255)
description = models.TextField(null=True, blank=False)
magazine = models.ForeignKey(Magazine, on_delete=models.CASCADE)
published_at = models.DateField(
null=False, blank=False, default=datetime.date.today
)

@property
def full_title(self) -> str:
return "{} #{} {}".format("Хакер", self.number, self.title or "")
12 changes: 12 additions & 0 deletions src/magplan/models/magazine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.db import models

from magplan.models.abs import AbstractBase


class Magazine(AbstractBase):
slug = models.SlugField(null=False, blank=False, max_length=255)
title = models.CharField(null=False, blank=False, max_length=255)
description = models.TextField(null=False, blank=True)

def __str__(self):
return self.title
Loading