5

This is my code

models.py(App: Article)

from django.contrib.auth.models import User

class Article(models.Model):
    # code...
    url_title = models.CharField(max_length=80, unique=True, db_index=True)


HATE_SPEECH = 'HS'
SPAM = 'SP'
FAKE_INFO = 'FI'

REPORT_REASON = (
    (FAKE_INFO, 'Fake Information'),
    (HATE_SPEECH, 'Hate Speech'),
    (SPAM, 'Spam'))

class Report(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    article_id = models.ForeignKey(Article, on_delete=models.PROTECT)
    user_id = models.ForeignKey(User, on_delete=models.PROTECT)
    reason = models.CharField(max_length=2, choices=REPORT_REASON)
    solved_status = models.BooleanField(default=False)
    date_created = models.DateTimeField(auto_now_add=now)

admin.py(App: Article)

class ArticleAdmin(admin.ModelAdmin):
    pass
    # code...

class ReportAdmin(admin.ModelAdmin):
    list_display = ('id', 'article_id', 'user_id', 'reason', 'solved_status', 'date_created')

admin.site.register(Article, ArticleAdmin)
admin.site.register(Report, ReportAdmin)

My database records currently looks like this:

enter image description here

In django admin I want to display all these records in such a manner.

  1. Unsolved queries should display first (which I am able to achieve using ordering = ['solved_status'])
  2. The article which is (i) not solved and (ii) reported the highest number of times should come first(Here article a1: because it is reported 3 times. Do NOT consider a2 because, in the last record, it is considered as solved
  3. Highest Number of same reason from the same article (Here Hate Speech is coming 2 times, so it should come first and then spam should come) NOTE: Do not consider Spam as 4 times because we have to fulfill condition 2 first.
  4. The article which is reported first should display first according to DateTime. (id: 8 was made first, then id 1) NOTE: Do not consider id 7 as oldest because we have to fulfill condition 1, 2 and 3 first.
  5. If the remaining record meets none of the condition from 1, 2 and 3, the report which was made first should display first according to DateTime.

So, the final Table should look something like this:

enter image description here

Now, we can see that

  1. Our first condition is fulfilled as solved queries are moved to last.
  2. Our second condition is fulfilled as a1 is moved to the top being the article with the highest reports raised.
  3. Our third condition is fulfilled because Hate speech is at the top being the reason with the highest reports raised in article a1.
  4. Our fourth condition is fulfilled as Id: 8 is moved to the top because this report was made prior to id: 1 considering the DateTimeField.
  5. Our fifth condition is fulfilled because id:5 and id:3 have nothing in common, but as id: 5 was prior, so it should come first.

I tried using annotate using this link which I thought could solve my partial doubt, but it is constantly giving me errors and doubt yet not being solved. I am trying this for a long time, any help is appreciable. Thanks in advance :)

aberezh
  • 68
  • 4
ajinzrathod
  • 925
  • 10
  • 28
  • @TomWojcik EDIT: I dont need the query of amazon. I just need want to know how those complicated queries can be solved in Django-admin. Where we can use `group by` or `partition by` with `count`[SQL terms] etc.. all together. Amazon is written just for giving an example – ajinzrathod Mar 31 '21 at 08:42

1 Answers1

1

Some window function annotations should enable the described ordering:

from django.db.models import F, Count, Window, Q

class ReportAdmin(admin.ModelAdmin):

    list_display = ('id', 'article_id', 'user_id', 'reason', 'solved_status', 'date_created',
                    'count_by_article', 'count_by_article_and_reason')

    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            count_by_article=Window(
                expression=Count('id', filter=Q(solved_status=False)),
                partition_by=F('article_id')
            ),
            count_by_article_and_reason=Window(
                expression=Count('id', filter=Q(solved_status=False)),
                partition_by=[F('article_id'), F('reason')],
            ),
            earliest_report_by_article=Window(
                expression=Min('date_created', filter=Q(solved_status=False)),
                partition_by=[F('article_id')],
            ),
            earliest_report_by_article_and_reason=Window(
                expression=Min('date_created', filter=Q(solved_status=False)),
                partition_by=[F('article_id'), F('reason')],
            ),
        ).order_by('solved_status', '-count_by_article', 'earliest_report_by_article', 'article_id',
                   '-count_by_article_and_reason', 'earliest_report_by_article_and_reason',
                   'reason', 'date_created')

    def count_by_article(self, obj):
        return obj.count_by_article

    def count_by_article_and_reason(self, obj):
        return obj.count_by_article_and_reason


admin.site.register(Report, ReportAdmin)
azundo
  • 5,902
  • 1
  • 14
  • 21
  • Your answer has given me a remarkable progress for my issue. But I still do face some problems. Letme explain 1st Problem: You can see I have written `Highest Number of same reason from the **same** article`. See [this](https://imgur.com/H27uO69). (See 1st and 2nd Record) I have 2 Record that Reports 2 different Articles. But the reason of both of them is same. So as per 4th Point `The article which is reported first should display first`. But it is not. I think its because it is counting Reports that are solved also or it is partitioning by _reason_ first. – ajinzrathod Apr 03 '21 at 11:11
  • 2nd Problem: All the lines I see are using `Count(*)` Read 2nd point: `Do NOT consider a2 because, in the last record, it is considered as solved` Solved reports should NOT be taken into consideration after acheiving First Point. They should only be displayed. Nothing else. – ajinzrathod Apr 03 '21 at 11:13
  • 3rd Problem: See [this](https://imgur.com/c10Mial) See the Top 4 records. Here, 2 Articles are reported 2 times Each and all those 4 records are given same reason. Now, it is should sort by date_created. But it is not. – ajinzrathod Apr 03 '21 at 11:15
  • Yes, I misunderstood about the solved status and thought it would apply to the whole article vs a report, so I've now corrected that by filtering out solved reports from the window functions. I think that also solves the other issues but I'm still not entirely clear. Am I correct that all unsolved reports for a given article should appear together? – azundo Apr 05 '21 at 06:00
  • It is showing me error like **Star cannot be used with filter. Please specify a field.** So I just changed `expression=Count('*'...` to `expression=Count('article_id'....` . Is this correct ? And yes I do think you are right and all my doubt is completely solved. I am just running some combinations to check that it is running in all cases. Will update soon – ajinzrathod Apr 05 '21 at 16:09
  • Thank you. You answer has made my points crystal clear. Can u just help me if I can get count of `count_by_article`, `count_by_article_and_reason` etc... in _django-admin_. So that in future when I make such queries, I can see the count or even use it for debugging. BTW. Thanks very much.... – ajinzrathod Apr 05 '21 at 16:17
  • If I have solved this(**Star cannot be used with filter. Please specify a field**) error in right way, please do edit the answer. – ajinzrathod Apr 05 '21 at 16:20
  • 1
    Ah, interesting, I don't think the restriction on `filter` and `COUNT(*)` exists on postgres but for specifying a field I think `id` is probably the simplest. I'll edit the answer, and also include code for adding the `count_by_article` and `count_by_article_and_reason` in the admin. – azundo Apr 05 '21 at 17:43
  • And just to clarify `COUNT(article_id)` is fine in this case since `article_id` can never be null so you don't need to change anything with your code. – azundo Apr 05 '21 at 17:50