Skip to content

Commit fb8bcdf

Browse files
authored
Merge pull request #175 from PROCOLLAB-github/dev
Фильтры по программам, небольшие оптимизации запросов
2 parents 721b3ee + 13e6923 commit fb8bcdf

25 files changed

Lines changed: 520 additions & 171 deletions

news/admin.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,59 @@
11
from django.contrib import admin
22

3-
from news.models import News, NewsTag
3+
from news.models import News
44

55

66
@admin.register(News)
77
class NewsAdmin(admin.ModelAdmin):
8-
list_display = ("id", "title", "tags_str")
9-
list_display_links = (
8+
# todo
9+
list_display = (
1010
"id",
11-
"title",
11+
"content_type",
12+
"object_id",
13+
"text",
14+
"datetime_created",
15+
"datetime_updated",
1216
)
13-
14-
15-
@admin.register(NewsTag)
16-
class NewsTagAdmin(admin.ModelAdmin):
17-
list_display = ("name", "description", "datetime_created", "datetime_updated", "id")
1817
list_display_links = (
1918
"id",
20-
"name",
19+
"content_type",
20+
"object_id",
21+
"text",
22+
"datetime_created",
23+
"datetime_updated",
24+
)
25+
list_filter = (
26+
"datetime_created",
27+
"datetime_updated",
28+
)
29+
search_fields = ("text",)
30+
readonly_fields = (
31+
"datetime_created",
32+
"datetime_updated",
2133
)
34+
# fieldsets = (
35+
# (
36+
# None,
37+
# {
38+
# "fields": (
39+
# "content_type",
40+
# "object_id",
41+
# "text",
42+
# "files",
43+
# )
44+
# },
45+
# ),
46+
# (
47+
# "Даты",
48+
# {
49+
# "fields": (
50+
# "datetime_created",
51+
# "datetime_updated",
52+
# )
53+
# },
54+
# ),
55+
# )
56+
# filter_horizontal = (
57+
# "files",
58+
# )
59+
# save_on_top = True

news/filters.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ class NewsFilter(filters.FilterSet):
1818
.filter(datetime_created__gt=datetime.datetime(...))
1919
"""
2020

21-
title__contains = filters.Filter(field_name="title", lookup_expr="contains")
21+
# title__contains = filters.Filter(field_name="title", lookup_expr="contains")
2222
text__contains = filters.Filter(field_name="text", lookup_expr="contains")
2323
datetime_created__gt = filters.DateTimeFilter(
2424
field_name="datetime_created", lookup_expr="gt"
2525
)
2626

2727
class Meta:
2828
model = News
29-
fields = ("title__contains", "text__contains", "datetime_created__gt")
29+
fields = (
30+
# "title__contains",
31+
"text__contains",
32+
"datetime_created__gt",
33+
)

news/managers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
class NewsManager(models.Manager):
66
def get_news(self, obj):
77
obj_type = ContentType.objects.get_for_model(obj)
8-
return self.get_queryset().filter(content_type=obj_type, object_id=obj.id)
8+
return self.get_queryset().filter(content_type=obj_type, object_id=obj.pk)
99

1010
def add_news(self, obj, **kwargs):
1111
obj_type = ContentType.objects.get_for_model(obj)
12-
news = self.create(content_type=obj_type, object_id=obj.id, **kwargs)
12+
news = self.create(content_type=obj_type, object_id=obj.pk, **kwargs)
1313
return news
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Generated by Django 4.2.3 on 2023-07-18 23:25
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("files", "0005_alter_userfile_options"),
11+
("contenttypes", "0002_remove_content_type_name"),
12+
("news", "0005_news_content_type_news_object_id"),
13+
]
14+
15+
operations = [
16+
migrations.AlterModelOptions(
17+
name="news",
18+
options={
19+
"ordering": ["-datetime_created"],
20+
"verbose_name": "Новость",
21+
"verbose_name_plural": "Новости",
22+
},
23+
),
24+
migrations.RemoveField(
25+
model_name="news",
26+
name="cover_url",
27+
),
28+
migrations.RemoveField(
29+
model_name="news",
30+
name="short_text",
31+
),
32+
migrations.RemoveField(
33+
model_name="news",
34+
name="tags",
35+
),
36+
migrations.RemoveField(
37+
model_name="news",
38+
name="title",
39+
),
40+
migrations.AddField(
41+
model_name="news",
42+
name="files",
43+
field=models.ManyToManyField(
44+
blank=True, related_name="news", to="files.userfile"
45+
),
46+
),
47+
migrations.AlterField(
48+
model_name="news",
49+
name="content_type",
50+
field=models.ForeignKey(
51+
on_delete=django.db.models.deletion.CASCADE,
52+
related_name="news",
53+
to="contenttypes.contenttype",
54+
),
55+
),
56+
migrations.AlterField(
57+
model_name="news",
58+
name="object_id",
59+
field=models.PositiveIntegerField(),
60+
),
61+
migrations.DeleteModel(
62+
name="NewsTag",
63+
),
64+
]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 4.2.3 on 2023-07-20 18:34
2+
3+
from django.db import migrations
4+
from django.db.migrations import RunPython
5+
6+
7+
def project_news_to_news_news(apps, schema_editor):
8+
# add ProjectNews model to news app via schema_editor
9+
ProjectNews = apps.get_model("projects.ProjectNews")
10+
News = apps.get_model("news", "News")
11+
ContentType = apps.get_model("contenttypes", "ContentType")
12+
for project_news in ProjectNews.objects.all():
13+
project_content_type = ContentType.objects.get(
14+
app_label="projects", model="project"
15+
)
16+
News.objects.create(
17+
text=project_news.text,
18+
content_type=project_content_type,
19+
object_id=project_news.project.id,
20+
files=project_news.files.all(),
21+
views=project_news.views.all(),
22+
likes=project_news.likes.all(),
23+
datetime_created=project_news.datetime_created,
24+
datetime_updated=project_news.datetime_updated
25+
)
26+
27+
28+
class Migration(migrations.Migration):
29+
30+
dependencies = [
31+
("news", "0006_alter_news_options_remove_news_cover_url_and_more"),
32+
("projects", "0013_projectnews")
33+
]
34+
35+
operations = [
36+
RunPython(project_news_to_news_news)
37+
]

news/models.py

Lines changed: 29 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,50 @@
1-
from django.contrib.contenttypes.fields import GenericForeignKey
1+
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
22
from django.contrib.contenttypes.models import ContentType
33
from django.db import models
44

5+
from core.models import Like, View
6+
from files.models import UserFile
57
from news.managers import NewsManager
68

79

8-
class NewsTag(models.Model):
9-
"""News tag model
10-
11-
Attributes:
12-
name: A CharField for the tag's name
13-
description: A CharField for the tag's description
14-
datetime_created: A DateTimeField indicating date of creation.
15-
datetime_updated: A DateTimeField indicating date of update.
16-
"""
17-
18-
name = models.CharField("Название тега", max_length=256, blank=False, null=False)
19-
# hopefully 512 characters are enough for any tag description
20-
description = models.CharField("Описание тега", max_length=512, blank=True, null=True)
21-
22-
# probably not really needed here but won't hurt
23-
datetime_created = models.DateTimeField(
24-
verbose_name="Дата создания", null=False, auto_now_add=True
25-
)
26-
datetime_updated = models.DateTimeField(
27-
verbose_name="Дата изменения", null=False, auto_now=True
28-
)
29-
30-
def __str__(self):
31-
return f"NewsTag<{self.id}> - {self.name}"
32-
33-
class Meta:
34-
verbose_name = "Тег"
35-
verbose_name_plural = "Теги"
36-
37-
3810
class News(models.Model):
39-
"""
40-
News model
41-
42-
Attributes:
43-
title: A CharField news title.
44-
text: A TextField news text content.
45-
short_text: A TextField news short text content.
46-
cover_url: A URLField link to news cover image.
47-
tags: A ManyToManyField listing all tags of this news object
48-
datetime_created: A DateTimeField indicating date of creation.
49-
datetime_updated: A DateTimeField indicating date of update.
50-
"""
51-
52-
title = models.CharField(max_length=256, null=False)
53-
text = models.TextField(null=False)
54-
short_text = models.TextField(max_length=256, blank=True)
55-
cover_url = models.URLField(null=False)
56-
57-
tags = models.ManyToManyField(NewsTag, blank=True, verbose_name="Список тегов")
58-
59-
# generic relation to project/program/ something else possibly
6011
content_type = models.ForeignKey(
61-
ContentType, on_delete=models.CASCADE, related_name="news", null=True, blank=True
12+
ContentType,
13+
on_delete=models.CASCADE,
14+
related_name="news",
6215
)
63-
object_id = models.PositiveIntegerField(null=True, blank=True)
16+
object_id = models.PositiveIntegerField()
6417
content_object = GenericForeignKey("content_type", "object_id")
6518

19+
text = models.TextField(
20+
null=False,
21+
blank=False,
22+
)
23+
files = models.ManyToManyField(UserFile, related_name="news", blank=True)
24+
25+
views = GenericRelation(
26+
View,
27+
related_query_name="project_views",
28+
)
29+
likes = GenericRelation(
30+
Like,
31+
related_query_name="project_news",
32+
)
33+
6634
datetime_created = models.DateTimeField(
67-
verbose_name="Дата создания", null=False, auto_now_add=True
35+
verbose_name="Дата создания",
36+
null=False,
37+
auto_now_add=True,
6838
)
6939
datetime_updated = models.DateTimeField(
70-
verbose_name="Дата изменения", null=False, auto_now=True
40+
verbose_name="Дата изменения",
41+
null=False,
42+
auto_now=True,
7143
)
7244

7345
objects = NewsManager()
7446

75-
@property
76-
def tags_str(self):
77-
"""Formats tags to string
78-
79-
Returns: List of tags' names separated by a comma
80-
"""
81-
return ", ".join([i.name for i in self.tags.all()])
82-
83-
def __str__(self):
84-
return f"News<{self.id}> - {self.title}"
85-
8647
class Meta:
8748
verbose_name = "Новость"
8849
verbose_name_plural = "Новости"
50+
ordering = ["-datetime_created"]

news/pagination.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from rest_framework import pagination
2+
3+
4+
class NewsPagination(pagination.LimitOffsetPagination):
5+
"""
6+
Pagination for News
7+
8+
For example:
9+
/news/?limit=10&offset=10
10+
gets the next 10 news after the first 10 news.
11+
"""
12+
13+
default_limit = 10
14+
limit_query_param = "limit"
15+
offset_query_param = "offset"

news/permissions.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from rest_framework import permissions
2+
from rest_framework.permissions import SAFE_METHODS
3+
4+
from projects.models import Project
5+
6+
7+
class IsNewsCreatorOrReadOnly(permissions.BasePermission):
8+
def has_object_permission(self, request, view, obj):
9+
"""
10+
read/update/delete permission
11+
currently can only be updated/deleted in admin panel
12+
"""
13+
if request.method in SAFE_METHODS:
14+
return True
15+
if isinstance(obj.content_object, Project):
16+
# it's a project
17+
if obj.content_object.leader == request.user:
18+
return True
19+
else:
20+
# it's a partner program
21+
# TODO: implement
22+
pass
23+
return False
24+
25+
def has_permission(self, request, view):
26+
"""
27+
Creation permission
28+
Currently can only be created via admin panel
29+
"""
30+
# everybody can read this
31+
if request.method in SAFE_METHODS:
32+
return True
33+
34+
try:
35+
# try to judge it as a project
36+
project = Project.objects.get(pk=view.kwargs["project_pk"])
37+
if request.method in SAFE_METHODS or (request.user == project.leader):
38+
return True
39+
except Project.DoesNotExist:
40+
return False
41+
except KeyError:
42+
# It's a partner program, currently can only be created via admin
43+
# TODO: implement
44+
pass
45+
return False

0 commit comments

Comments
 (0)