Skip to content

Commit c3f4f34

Browse files
yokwejusteNdibe Raymond Olisaemeka
and
Ndibe Raymond Olisaemeka
authored
[backend] add activity to admin management (#1093)
* minor modifications to the admin model * add admin models management to admin.py * implement effective signals to handle admin models instances, addition, modification and deletion with proper cleanup Issue: #1094 Signed-off-by: Ndibe Raymond Olisaemeka <[email protected]> Co-authored-by: Ndibe Raymond Olisaemeka <[email protected]>
1 parent b1b7c8b commit c3f4f34

File tree

6 files changed

+414
-155
lines changed

6 files changed

+414
-155
lines changed
+101-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,102 @@
1-
from django.contrib import admin
1+
from django.contrib import admin, messages
22

3-
# Register your models here.
3+
from .models import (
4+
Activity,
5+
ActivityImage,
6+
ActivityMakingStep,
7+
Image,
8+
InspiringArtist,
9+
InspiringExample,
10+
)
11+
12+
13+
class InlineActivityImages(admin.StackedInline):
14+
model = ActivityImage
15+
16+
17+
class InlineActivityMakingSteps(admin.StackedInline):
18+
model = ActivityMakingStep
19+
20+
21+
class InlineInspiringExamples(admin.StackedInline):
22+
model = InspiringExample
23+
24+
25+
class InspiringArtistAdmin(admin.ModelAdmin):
26+
search_fields = ("name",)
27+
list_display = (
28+
"name",
29+
"image",
30+
)
31+
list_filter = ("name",)
32+
33+
34+
class ActivityAdmin(admin.ModelAdmin):
35+
list_display = ("title", "id", "created_on", "publish")
36+
list_filter = (
37+
"created_on",
38+
"publish",
39+
)
40+
search_fields = (
41+
"title",
42+
"id",
43+
"category",
44+
)
45+
ordering = ["-created_on"]
46+
actions = ["publish", "un_publish", "delete_selected"]
47+
inlines = [InlineActivityImages, InlineActivityMakingSteps, InlineInspiringExamples]
48+
list_per_page = 50 # paginate when more than 50 items
49+
50+
def un_publish(self, request, queryset):
51+
"""
52+
This function is used to unpublish selected activities
53+
"""
54+
queryset.update(publish=False)
55+
messages.success(request, "Selected records were unpublished successfully.")
56+
57+
def publish(self, request, queryset):
58+
"""
59+
This function is used to publish selected activities
60+
"""
61+
queryset = queryset.filter(publish=False)
62+
queryset.update(publish=True)
63+
messages.success(request, "Selected records were published successfully.")
64+
65+
def delete_selected(self, request, queryset):
66+
"""
67+
This function is used to delete selected activities
68+
"""
69+
queryset.delete()
70+
messages.success(request, "Selected records were deleted successfully.")
71+
72+
def get_readonly_fields(self, request, obj=None):
73+
return [
74+
"id",
75+
"created_on",
76+
"views_count",
77+
"saved_by",
78+
"views",
79+
"saved_count",
80+
"slug",
81+
]
82+
83+
un_publish.short_description = "Unpublish selected activities"
84+
publish.short_description = "Publish selected activities"
85+
delete_selected.short_description = "Delete selected activities"
86+
87+
88+
class ActivityImageAdmin(admin.ModelAdmin):
89+
search_fields = ["activity__title", "activity__id", "image__public_id"]
90+
list_display = ["activity", "image"]
91+
92+
93+
class ImageAdmin(admin.ModelAdmin):
94+
search_fields = ["public_id"]
95+
list_display = ["public_id", "file_url"]
96+
# should not be able to edit this from the admin panel ?
97+
98+
99+
admin.site.register(InspiringArtist, InspiringArtistAdmin)
100+
admin.site.register(Activity, ActivityAdmin)
101+
admin.site.register(ActivityImage, ActivityImageAdmin)
102+
admin.site.register(Image, ImageAdmin)

zubhub_backend/zubhub/activities/apps.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22

33

44
class ActivitiesConfig(AppConfig):
5-
name = 'activities'
5+
name = "activities"
6+
7+
def ready(self):
8+
import activities.signals # noqa: F401
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Generated by Django 3.2 on 2024-06-24 16:36
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("activities", "0017_alter_activity_materials_used"),
9+
]
10+
11+
operations = [
12+
migrations.AlterModelOptions(
13+
name="activity",
14+
options={"verbose_name_plural": "Activities"},
15+
),
16+
]

zubhub_backend/zubhub/activities/models.py

+81-67
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
import uuid
2-
from django.db import models
2+
from math import floor
3+
34
from django.contrib.auth import get_user_model
4-
from django.utils.text import slugify
5+
from django.db import models
56
from django.utils import timezone
6-
from math import floor
7-
from projects.models import Category
7+
from django.utils.text import slugify
88

99
Creator = get_user_model()
1010

1111

12+
# TODO: use a single model for images everywhere
1213
class Image(models.Model):
1314
file_url = models.URLField(max_length=1000)
1415
public_id = models.TextField(max_length=1000, blank=True)
1516

1617
def __str__(self):
1718
try:
18-
image = self.file_url
19+
file_url = self.file_url
1920
except AttributeError:
20-
image = ''
21-
return "Photo <%s:%s>" % (self.public_id, image)
21+
file_url = ""
22+
return "Photo <%s:%s>" % (self.public_id, file_url)
2223

2324

2425
class InspiringArtist(models.Model):
25-
'''this should be having more fields to distinguish an artist '''
26-
image = models.ForeignKey(Image,
27-
on_delete=models.CASCADE,
28-
null=True,
29-
blank=True)
26+
"""this should be having more fields to distinguish an artist"""
27+
28+
image = models.ForeignKey(
29+
Image, on_delete=models.CASCADE, null=True, blank=True
30+
) # TODO: change to OneToOneField
3031
short_biography = models.TextField(max_length=10000, blank=True, null=True)
3132
name = models.CharField(max_length=100, null=True)
3233

@@ -35,41 +36,45 @@ def __str__(self):
3536

3637

3738
class Activity(models.Model):
38-
id = models.UUIDField(primary_key=True,
39-
default=uuid.uuid4,
40-
editable=False,
41-
unique=True)
42-
creators = models.ManyToManyField(Creator,
43-
related_name="activities_created")
39+
id = models.UUIDField(
40+
primary_key=True, default=uuid.uuid4, editable=False, unique=True
41+
)
42+
creators = models.ManyToManyField(Creator, related_name="activities_created")
4443
title = models.CharField(max_length=500)
45-
category = models.ManyToManyField("projects.Category",blank=True, related_name="activities")
46-
introduction = models.CharField(max_length=10000,blank=True)
44+
category = models.ManyToManyField(
45+
"projects.Category", blank=True, related_name="activities"
46+
)
47+
introduction = models.CharField(max_length=10000, blank=True)
4748
class_grade = models.CharField(max_length=50, blank=True)
48-
49+
4950
learning_goals = models.TextField(max_length=10000, blank=True, null=True)
5051
facilitation_tips = models.TextField(max_length=10000, blank=True, null=True)
5152
motivation = models.TextField(max_length=10000, blank=True, null=True)
5253
video = models.URLField(max_length=1000, blank=True, null=True)
5354
materials_used = models.TextField(max_length=5000, blank=True, null=True)
54-
materials_used_image = models.ForeignKey(Image,
55-
on_delete=models.SET_NULL,
56-
null=True,
57-
blank=True,
58-
)
59-
inspiring_artist = models.ForeignKey(InspiringArtist,
60-
on_delete=models.SET_NULL,
61-
null=True,
62-
related_name="inspiring_artist_activities",
63-
blank=True,
64-
)
65-
views = models.ManyToManyField(Creator,
66-
blank=True,
67-
related_name="activities_viewed")
55+
materials_used_image = models.ForeignKey( # TODO: change to OneToOneField
56+
Image,
57+
on_delete=models.SET_NULL,
58+
null=True,
59+
blank=True,
60+
)
61+
inspiring_artist = (
62+
models.ForeignKey( # TODO: sure an activity can only be inspired by one artist?
63+
InspiringArtist,
64+
on_delete=models.SET_NULL,
65+
null=True,
66+
related_name="activities_inspired",
67+
blank=True,
68+
)
69+
)
70+
views = models.ManyToManyField(
71+
Creator, blank=True, related_name="activities_viewed"
72+
)
6873
views_count = models.IntegerField(blank=True, default=0)
6974
saved_count = models.IntegerField(blank=True, default=0)
70-
saved_by = models.ManyToManyField(Creator,
71-
blank=True,
72-
related_name="activities_saved")
75+
saved_by = models.ManyToManyField(
76+
Creator, blank=True, related_name="activities_saved"
77+
)
7378
created_on = models.DateTimeField(default=timezone.now, null=True)
7479
publish = models.BooleanField(default=False, null=True)
7580
slug = models.SlugField(unique=True, max_length=1000)
@@ -79,56 +84,65 @@ def save(self, *args, **kwargs):
7984
pass
8085
else:
8186
uid = str(uuid.uuid4())
82-
uid = uid[0:floor(len(uid) / 6)]
87+
uid = uid[0 : floor(len(uid) / 6)]
8388
self.slug = slugify(self.title) + "-" + uid
8489

8590
super().save(*args, **kwargs)
8691

8792
def __str__(self):
8893
return self.title
8994

95+
class Meta:
96+
verbose_name_plural = "Activities"
97+
9098

9199
class InspiringExample(models.Model):
92-
activity = models.ForeignKey(Activity,
93-
on_delete=models.CASCADE,
94-
null=True,
95-
related_name="inspiring_examples",
96-
blank=True)
100+
activity = models.ForeignKey(
101+
Activity,
102+
on_delete=models.CASCADE,
103+
null=True,
104+
related_name="inspiring_examples",
105+
blank=True,
106+
)
97107
description = models.TextField(max_length=10000, blank=True)
98108
credit = models.TextField(max_length=1000, blank=True)
99-
image = models.ForeignKey(Image,
100-
on_delete=models.CASCADE,
101-
null=True,
102-
blank=True)
109+
image = models.ForeignKey(
110+
Image, on_delete=models.CASCADE, null=True, blank=True
111+
) # TODO: change to OneToOneField
103112

104113
def __str__(self):
105-
return self.image
114+
return self.image.file_url
106115

107116

108117
class ActivityImage(models.Model):
109-
activity = models.ForeignKey(Activity,
110-
on_delete=models.CASCADE,
111-
null=True,
112-
related_name="activity_images",
113-
blank=True)
114-
image = models.ForeignKey(Image,
115-
on_delete=models.CASCADE,
116-
null=True,
117-
blank=True)
118+
activity = models.ForeignKey(
119+
Activity,
120+
on_delete=models.CASCADE,
121+
null=True,
122+
related_name="activity_images",
123+
blank=True,
124+
)
125+
image = models.ForeignKey(
126+
Image, on_delete=models.CASCADE, null=True, blank=True
127+
) # TODO: change to OneToOneField
118128

119129
def __str__(self):
120-
return self.image
130+
return self.image.file_url
121131

122132

123133
class ActivityMakingStep(models.Model):
124-
activity = models.ForeignKey(Activity,
125-
on_delete=models.CASCADE,
126-
null=True,
127-
related_name="making_steps",
128-
blank=True)
129-
130-
title = models.TextField(max_length=500,null=True)
131-
image = models.ManyToManyField(Image,blank=True)
134+
activity = models.ForeignKey(
135+
Activity,
136+
on_delete=models.CASCADE,
137+
null=True,
138+
related_name="making_steps",
139+
blank=True,
140+
)
141+
142+
title = models.TextField(max_length=500, null=True)
143+
image = models.ManyToManyField(
144+
Image, blank=True
145+
) # TODO: should this be ManyToManyField or OneToOneField ?
132146
description = models.TextField(max_length=10000, blank=True)
133147
step_order = models.IntegerField()
134148

0 commit comments

Comments
 (0)