diff --git a/zubhub_backend/zubhub/activities/admin.py b/zubhub_backend/zubhub/activities/admin.py index 8c38f3f3d..fd0c11586 100644 --- a/zubhub_backend/zubhub/activities/admin.py +++ b/zubhub_backend/zubhub/activities/admin.py @@ -1,3 +1,120 @@ -from django.contrib import admin +from django.conf import settings +from django.contrib import admin, messages -# Register your models here. +from .models import Activity, ActivityImage, ActivityMakingStep, Image +from .tasks import delete_file_task + + +class ActivityAdmin(admin.ModelAdmin): + list_display = ("title", "id", "created_on", "publish") + list_filter = ( + "created_on", + "publish", + ) + search_fields = ( + "title", + "id", + "category", + ) + ordering = ["-created_on"] + actions = ["publish", "un_publish", "delete_selected"] + + def un_publish(self, request, queryset): + """ + This function is used to unpublish selected activities + """ + queryset.update(publish=False) + messages.success(request, "Selected records were unpublished successfully.") + + def publish(self, request, queryset): + """ + This function is used to publish selected activities + """ + queryset = queryset.filter(publish=False) + queryset.update(publish=True) + messages.success(request, "Selected records were published successfully.") + + def delete_selected(self, request, queryset): + """ + This function is used to delete selected activities + """ + queryset.delete() + messages.success(request, "Selected records were deleted successfully.") + + un_publish.short_description = "Unpublish selected activities" + publish.short_description = "Publish selected activities" + delete_selected.short_description = "Delete selected activities" + + def get_readonly_fields(self, request, obj=None): + return ["id", "created_on", "views_count", "saved_by", "views"] + + def save_model(self, request, obj, form, change): + super().save_model(request, obj, form, change) + + if change: + old = Activity.objects.get(pk=obj.pk) + new = Activity.objects.get(pk=obj.pk) + + if ( + old.video.find("cloudinary.com") > -1 + or old.video.startswith( + "{0}://{1}".format( + settings.DEFAULT_MEDIA_SERVER_PROTOCOL, + settings.DEFAULT_MEDIA_SERVER_DOMAIN, + ) + ) + ) and old.video != new.video: + delete_file_task.delay(old.video) + + +class ActivityImageAdmin(admin.ModelAdmin): + search_fields = ["activity__title", "activity__id", "image__public_id"] + list_display = ["activity", "image"] + + def delete_model(self, request, obj): + """ + This function is used to delete the activity image + """ + delete_file_task.delay(obj.image.file_url) + obj.delete() + + def delete_queryset(self, request, queryset): + """ + This function is used to delete the activity image + """ + for obj in queryset: + delete_file_task.delay(obj.image.file_url) + queryset.delete() + + +class ImageAdmin(admin.ModelAdmin): + search_fields = ["public_id"] + list_display = ["public_id", "file_url"] + + def delete_model(self, request, obj): + """ + This function is used to delete the image + """ + delete_file_task.delay(obj.file_url) + obj.delete() + + +class ActivityMakingStepAdmin(admin.ModelAdmin): + search_fields = ["activity__title", "title"] + list_display = ["activity", "title"] + list_filter = ["activity", "title"] + + def delete_model(self, request, obj): + """ + This function is used to delete the activity making step + """ + for img in obj.image.all(): + delete_file_task.delay(img.file_url) + img.delete() + obj.delete() + + +admin.site.register(Activity, ActivityAdmin) +admin.site.register(ActivityImage, ActivityImageAdmin) +admin.site.register(Image, ImageAdmin) +admin.site.register(ActivityMakingStep, ActivityMakingStepAdmin) diff --git a/zubhub_backend/zubhub/activities/models.py b/zubhub_backend/zubhub/activities/models.py index 6251fd589..19291026b 100644 --- a/zubhub_backend/zubhub/activities/models.py +++ b/zubhub_backend/zubhub/activities/models.py @@ -1,10 +1,10 @@ import uuid -from django.db import models +from math import floor + from django.contrib.auth import get_user_model -from django.utils.text import slugify +from django.db import models from django.utils import timezone -from math import floor -from projects.models import Category +from django.utils.text import slugify Creator = get_user_model() @@ -17,16 +17,14 @@ def __str__(self): try: image = self.file_url except AttributeError: - image = '' + image = "" return "Photo <%s:%s>" % (self.public_id, image) class InspiringArtist(models.Model): - '''this should be having more fields to distinguish an artist ''' - image = models.ForeignKey(Image, - on_delete=models.CASCADE, - null=True, - blank=True) + """this should be having more fields to distinguish an artist""" + + image = models.ForeignKey(Image, on_delete=models.CASCADE, null=True, blank=True) short_biography = models.TextField(max_length=10000, blank=True, null=True) name = models.CharField(max_length=100, null=True) @@ -35,41 +33,43 @@ def __str__(self): class Activity(models.Model): - id = models.UUIDField(primary_key=True, - default=uuid.uuid4, - editable=False, - unique=True) - creators = models.ManyToManyField(Creator, - related_name="activities_created") + id = models.UUIDField( + primary_key=True, default=uuid.uuid4, editable=False, unique=True + ) + creators = models.ManyToManyField(Creator, related_name="activities_created") title = models.CharField(max_length=500) - category = models.ManyToManyField("projects.Category",blank=True, related_name="activities") - introduction = models.CharField(max_length=10000,blank=True) + category = models.ManyToManyField( + "projects.Category", blank=True, related_name="activities" + ) + introduction = models.CharField(max_length=10000, blank=True) class_grade = models.CharField(max_length=50, blank=True) - + learning_goals = models.TextField(max_length=10000, blank=True, null=True) facilitation_tips = models.TextField(max_length=10000, blank=True, null=True) motivation = models.TextField(max_length=10000, blank=True, null=True) video = models.URLField(max_length=1000, blank=True, null=True) materials_used = models.TextField(max_length=5000, blank=True, null=True) - materials_used_image = models.ForeignKey(Image, - on_delete=models.SET_NULL, - null=True, - blank=True, - ) - inspiring_artist = models.ForeignKey(InspiringArtist, - on_delete=models.SET_NULL, - null=True, - related_name="inspiring_artist_activities", - blank=True, - ) - views = models.ManyToManyField(Creator, - blank=True, - related_name="activities_viewed") + materials_used_image = models.ForeignKey( + Image, + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + inspiring_artist = models.ForeignKey( + InspiringArtist, + on_delete=models.SET_NULL, + null=True, + related_name="inspiring_artist_activities", + blank=True, + ) + views = models.ManyToManyField( + Creator, blank=True, related_name="activities_viewed" + ) views_count = models.IntegerField(blank=True, default=0) saved_count = models.IntegerField(blank=True, default=0) - saved_by = models.ManyToManyField(Creator, - blank=True, - related_name="activities_saved") + saved_by = models.ManyToManyField( + Creator, blank=True, related_name="activities_saved" + ) created_on = models.DateTimeField(default=timezone.now, null=True) publish = models.BooleanField(default=False, null=True) slug = models.SlugField(unique=True, max_length=1000) @@ -79,7 +79,7 @@ def save(self, *args, **kwargs): pass else: uid = str(uuid.uuid4()) - uid = uid[0:floor(len(uid) / 6)] + uid = uid[0 : floor(len(uid) / 6)] self.slug = slugify(self.title) + "-" + uid super().save(*args, **kwargs) @@ -87,48 +87,51 @@ def save(self, *args, **kwargs): def __str__(self): return self.title + class Meta: + verbose_name_plural = "Activities" + class InspiringExample(models.Model): - activity = models.ForeignKey(Activity, - on_delete=models.CASCADE, - null=True, - related_name="inspiring_examples", - blank=True) + activity = models.ForeignKey( + Activity, + on_delete=models.CASCADE, + null=True, + related_name="inspiring_examples", + blank=True, + ) description = models.TextField(max_length=10000, blank=True) credit = models.TextField(max_length=1000, blank=True) - image = models.ForeignKey(Image, - on_delete=models.CASCADE, - null=True, - blank=True) + image = models.ForeignKey(Image, on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return self.image class ActivityImage(models.Model): - activity = models.ForeignKey(Activity, - on_delete=models.CASCADE, - null=True, - related_name="activity_images", - blank=True) - image = models.ForeignKey(Image, - on_delete=models.CASCADE, - null=True, - blank=True) + activity = models.ForeignKey( + Activity, + on_delete=models.CASCADE, + null=True, + related_name="activity_images", + blank=True, + ) + image = models.ForeignKey(Image, on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return self.image class ActivityMakingStep(models.Model): - activity = models.ForeignKey(Activity, - on_delete=models.CASCADE, - null=True, - related_name="making_steps", - blank=True) - - title = models.TextField(max_length=500,null=True) - image = models.ManyToManyField(Image,blank=True) + activity = models.ForeignKey( + Activity, + on_delete=models.CASCADE, + null=True, + related_name="making_steps", + blank=True, + ) + + title = models.TextField(max_length=500, null=True) + image = models.ManyToManyField(Image, blank=True) description = models.TextField(max_length=10000, blank=True) step_order = models.IntegerField() diff --git a/zubhub_backend/zubhub/activities/signals.py b/zubhub_backend/zubhub/activities/signals.py new file mode 100644 index 000000000..8615072ab --- /dev/null +++ b/zubhub_backend/zubhub/activities/signals.py @@ -0,0 +1,18 @@ +import os + +from django.conf import settings +from django.db.models.signals import pre_delete +from django.dispatch import receiver + +from .models import Activity, ActivityImage, Image + + +@receiver(pre_delete, sender=Activity) +def delete_activity_images(sender, instance, **kwargs): + ActivityImage.objects.filter(activity=instance).delete() + + images = Image.objects.filter(activity=instance) + for image in images: + if os.path.isfile(os.path.join(settings.MEDIA_ROOT, str(image.file))): + os.remove(os.path.join(settings.MEDIA_ROOT, str(image.file))) + images.delete() diff --git a/zubhub_backend/zubhub/activities/tasks.py b/zubhub_backend/zubhub/activities/tasks.py new file mode 100644 index 000000000..547714d8a --- /dev/null +++ b/zubhub_backend/zubhub/activities/tasks.py @@ -0,0 +1,18 @@ +from random import uniform + +from celery import shared_task + + +@shared_task(bind=True, acks_late=True, max_retries=10) +def delete_file_task(self, url): + from zubhub.utils import delete_file_from_media_server + + try: + res = delete_file_from_media_server(url) + + res = res.json() + if res["result"] != "ok" and res["result"]["HTTPStatusCode"] != 204: + raise Exception() + + except Exception as e: + raise self.retry(exc=e, countdown=int(uniform(2, 4) ** self.request.retries))