Rank of users - Django - python

How can I rank the users based on their total number of posts.
example First : total number of posts 1-10
Second : total number of posts 11-20
Third : total number of posts 21-30
models.py
from django.db import models
User = get_user_model()
class Post(models.Model):
author = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
def get_absolute_url(self):
return reverse("post_detail",kwargs={'pk':self.pk})
def __str__(self):
return self.title
def count_posts_of(user):
return Post.objects.filter(author=user).count()

Ordering Users by the number of posts
We can annotate a User with the number of Posts, and then use order_by on that annotation:
from django.db.models import Count
User.objects.annotate(nposts=Count('post')).order_by('-nposts')
Here the dash (-) means that we sort in descending order (so from many posts to few posts). If you remove the dash, then it orders in ascending order.
Assigning a numerical rank to the Users
We can also assign a numerical rank to each user (so 1-10 map to 1, 11-20 map to 2, etc.) by adding some extra annotation:
from django.db.models import Count, F
from django.db.models.expressions import Func
User.objects.annotate(
nposts=Count('post'),
nrank=Func(F('nposts') / 10, function='CEIL'),
).order_by('-nposts')
Mapping the numeric rank to an textual rank
We can map this rank to a textual rank, by defining for example a #property:
from django.contrib.auth import User
RANK_TEXTS = ['zero', 'first', 'second', 'third']
def rank_text(self):
nrank = getattr(self, 'nrank', None)
if nrank is None:
nrank = (Post.objects(author=self).count() + 9) // 10
return RANK_TEXTS[nrank]
User.rank_text = property(rank_text)
So we monkey patch the User class such that it has a property rank_text. This will first look if we annotated the User with the nrank attribute. If that is not the case, we calculate the nrank manually. Finally we return the textual counterpart.
So we can for example query with:
u1 = User.objects.first()
u1.rank_text # for example "second"
Se here [so-post] how to monkey patch Django models.

Related

Django - Annotate with Case/When/Value and related objects

I have the two following models:
class Post(models.Model):
content = models.TextField()
class Vote(models.Model):
UP_VOTE = 0
DOWN_VOTE = 1
VOTE_TYPES = (
(UP_VOTE, "Up vote"),
(DOWN_VOTE, "Down vote"),
)
post = models.ForeignKey(Post, related_name="votes")
vote_type = models.PositiveSmallIntegerField(choices=VOTE_TYPES)
I would like to have a score property on Post that returns the sum of the values of the votes to that post, counting votes with UP_VOTE type as 1 and those with DOWN_VOTE as -1.
This is what I’ve tried:
# inside Post
#property
def score(self):
return (
self.votes.all()
.annotate(
value=Case(
When(vote_type=Vote.DOWN_VOTE, then=Value(-1)),
When(vote_type=Vote.UP_VOTE, then=Value(1)),
default=Value("0"),
output_field=models.SmallIntegerField(),
)
)
.aggregate(Sum("value"))["value__sum"]
)
However, this yields None. More specifically, without dereferencing ["value__sum"], this returns {'value__sum': None}.
Is using Case-When-Value the correct approach to my use case? If so, what’s wrong with the code I posted?
The sum of an empty set will be NULL/None by default. As of django-4.0, you can work with the default=… parameter [Django-doc]:
from django.db.models import F, Sum
#property
def score(self):
return self.votes.aggregate(total=Sum(-2*F('vote_type') + 1, default=0))['total']
Prior to django-4.0, you can work with Coalesce [Django-doc]:
from django.db.models import F, Sum, Value
from django.db.models.functions import Coalesce
#property
def score(self):
return self.votes.aggregate(
total=Coalesce(Sum(-2*F('vote_type') + 1), Value(0))
)['total']
although in this simple case, you can just replace None by 0 at the Django/Python layer:
from django.db.models import F, Sum
#property
def score(self):
return self.votes.aggregate(total=Sum(-2*F('vote_type') + 1))['total'] or 0
It might be better to use the "score" of a vote as value, so:
class Vote(models.Model):
UP_VOTE = 1
DOWN_VOTE = -1
# …
vote_type = models.SmallIntegerField(choices=VOTE_TYPES)
This will make the aggregation logic simpler, and will make it easier to later allow for example voting +5, -10, etc.

Django perform Annotate , count and Compare

I have following Model.
class Gallery(BaseModel):
company = models.ForeignKey(to=Company, on_delete=models.CASCADE)
image = models.ImageField(
upload_to=upload_company_image_to,
validators=[validate_image]
)
def __str__(self):
return f'{self.company.name}'
I want to allow maximum upto 5 image to be uploaded by one company so I tried my query as
def clean(self):
print(Gallery.objects.values('company').annotate(Count('image')).count())
I don't know how to compare above query with Integer 5. How do I do that?
You can retrieve the Companys that have more than five images with:
from django.db.models import Count
Company.objects.alias(
num_images=Count('gallery')
).filter(num_images__gt=5)
Prior to django-3.2, you can work with .annotate(…) [Django-doc]:
from django.db.models import Count
Company.objects.annotate(
num_images=Count('gallery')
).filter(num_images__gt=5)

How to fetch data from another app Django

I'm creating a quiz project using Django with MongoEngine. Multiple Choice Questions and quiz are two separated apps. I want to fetch multiple choice in quiz based on m_id (unique number for each multiple choice). I'm a beginner and need a little help. How can i achieve thisMCQS Model
from django_mongoengine import Document, EmbeddedDocument, fields
class multichoice(Document):
m_id = fields.IntField(min_value=1, verbose_name='MCQ Id')
m_title = fields.StringField(primary_key=True, max_length=255, verbose_name='Title')
m_question = fields.StringField(verbose_name='Question')
m_alternatives = fields.ListField(fields.StringField(),verbose_name='Alternatives')
def __str__(self):
return self.m_title
Quiz Model
from django_mongoengine import Document, EmbeddedDocument, fields
from mcqs.models import *
class quiz(Document):
q_id = fields.IntField(min_value=1, verbose_name='Quiz ID')
q_title = fields.StringField(primary_key=True, max_length=255, verbose_name='Title')
q_s_description = fields.StringField(max_length=255, verbose_name='Description')
q_questions = fields.ListField(fields.IntField(), verbose_name='Question Numbers', blank=True)
def __str__(self):
return self.q_title
MCQ Views
def view_multichoice(request, m_id):
get_md = multichoice.objects(m_question_number = m_id)
return render(request, 'mcqs/mcq.html', {'get_md':get_md})
Quiz Views
def view_quiz(request, q_id):
get_q = quiz.objects(q_id = q_id)
return render(request, 'quiz/quiz.html', {'get_q':get_q})
Current Result
Expected Result
EDIT 1
Quiz Views
from django.shortcuts import render
from django.http import HttpResponse
from .models import *
from mcqs.models import *
def view_quiz(request, q_id):
quiz_object = quiz.objects(q_id=q_id)[0]
multichoice_objects = [multichoice.objects(m_id=id) for id in quiz_object.q_questions]
get_q = [objects[0].m_question for objects in multichoice_objects if objects]
return render(request, 'quiz/quiz.html', {'get_q':get_q})
Quiz Template
{{ get_q }}
You are returning the question documents as it is. What you should be doing is, fetch the multichoice documents corresponding to the question ids, and then get the question field from each of those documents.
Change the second line in quiz views to this:
# Get the question document corresponding to given id
# objects method returns a list. Get the first one out of it.
# Here, I've assumed there's exactly 1 quiz document per ID. Handle the edge cases
quiz_object = quiz.objects(q_id = q_id)[0]
# Get the multiple choice documents corresponding to the ids in q_questions list
# Remember that objects method returns a list. Hence this is a list of lists
multichoice_objects = [multichoice.objects(m_id=id) for id in quiz_object.q_questions]
# Get the actual questions from the multichoice documents
get_q = [objects[0].m_question for objects in multichoice_objects if objects]
Ideally, you should be making multichoice as an EmbeddedDocument and the q_questions field in quiz model as EmbeddedDocumentListField. But the drawback here will be, you can't query EmbeddedDocuments independently. So you wont be able to do multichoice.objects(m_question_number=m_id).
You can read more about EmbeddedDocuments here

Can't sort table by model's property (accessor set)

I can't sort table by it's models property. I know that I should set accessor in the column so django-tables2 knows what field to process but it does not work.
This is the table:
class ScansTable(tables.Table):
site = tables.columns.Column(accessor='occurence.site', verbose_name='Site')
url = tables.columns.TemplateColumn("""{{ record.occurence.url|truncatechars:20 }}""",
accessor='occurence.url', verbose_name='Url')
price = tables.columns.TemplateColumn(u"""{{ record.price }} €""")
date = tables.columns.Column(accessor='date',order_by='date')
time = tables.columns.Column(accessor='time',order_by='time')
class Meta:
model = Scan
fields = ('date', 'time', 'site', 'url', 'valid', 'price')
attrs = {'id': 'cans_table',
'class': 'table',}
This is the Scan model:
class Scan(models.Model):
occurence = models.ForeignKey('Occurence', related_name='scans')
datetime = models.DateTimeField()
price = models.DecimalField(max_digits=20,decimal_places=2,null=True,blank=True,verbose_name='Price')
valid = models.BooleanField(default=True,verbose_name='Valid')
def __unicode__(self):
return u'{} {} {} {}'.format(self.occurence, self.datetime, self.price, u'OK' if self.valid else 'NOK')
#property
def date(self):
return self.datetime.date()
#property
def time(self):
return self.datetime.time()
The view:
def scans(request):
...
scans = Scan.objects.filter(occurence__product=product)
scans_table = ScansTable(scans)
RequestConfig(request).configure(scans_table)
scans_table.paginate(page=request.GET.get('page', 1), per_page=50)
return render(request,"dashboard_app/scans.html",context={'scans_table':scans_table})
The table is being properly renderd when I don't want to sort it. When I click on time (for example), it returns:
Cannot resolve keyword u'time' into field. Choices are: datetime,
groups, id, occurence, occurence_id, price, valid
Do you know where is the problem?
it's strange what the type product ?? you show the Occurence model and what value it in the view
It appears that defined properties/methods of the model are not available for sorting/filtering within the queryset. I don't fully understand why that is the case. A solution would be to NOT define date and time as properties on the Scan model, but instead annotate them to the queryset used to populate the data.
from django.db import models
def scans(request):
...
scans = Scan.objects.filter(occurence__product=product).annotate(
date=models.F('datetime__date'),
time=models.F('datetime__time')
)
...
See the documentation here on field lookups. Also you could use the tables specific columns for those fields - note that you don't need to define the accessors now the results are already in the queryset:
class ScansTable(tables.Table):
...
date = tables.DateColumn()
time = tables.TimeColumn()
...

django - how to sort objects alphabetically by first letter of name field

I have a model which has the fields word and definition. model of dictionary.
in db, i have for example these objects:
word definition
-------------------------
Banana Fruit
Apple also Fruit
Coffee drink
I want to make a query which gives me, sorting by the first letter of word, this:
Apple - also Fruit
Banana - Fruit
Coffee -drink
this is my model:
class Wiki(models.Model):
word = models.TextField()
definition = models.TextField()
I want to make it in views, not in template. how is this possible in django?
Given the model...
class Wiki(models.Model):
word = models.TextField()
definition = models.TextField()
...the code...
my_words = Wiki.objects.order_by('word')
...should return the records in the correct order.
However, you won't be able to create an index on the word field if the type is TextField, so sorting by word will take a long time if there are a lot of rows in your table.
I'd suggest changing it to...
class Wiki(models.Model):
word = models.CharField(max_length=255, unique=True)
definition = models.TextField()
...which will not only create an index on the word column, but also ensure you can't define the same word twice.
Since you tagged your question Django, I will answer how to do it using Django entities.
First, define your entity like:
class FruitWords(models.Model):
word = models.StringField()
definition = models.StringField()
def __str__(self):
return "%s - %s" % (self.word, self.definition)
To get the list:
for fruit in FruitWords.all_objects.order_by("word"):
print str(fruit)
If you are using class based ListView
class WikiListView(ListView):
model = Wiki
template_name = # Path to your html code. Example: 'appName/htmlFileName.html
def get_context_data(self, *args, **kwargs):
wiki_list = Wiki.objects.order_by('word')
context = super(WikiListView, self).get_context_data(*args, **kwargs)
context["wiki_list"] = wiki_list
return context
if you are using a simple view
def WikiView(request):
wiki_list = Wiki.objects.order_by('word')
return render(request, """HTML File""", {'wiki_list': wiki_list})
for example, I have an Article object that have a title field.
articles = list(sorted(articles, key=lambda obj:obj.title))
the issue you may run into is that you must require to return a QuerySet in class method occasions like get_queryset, the solution is stopping using a class based view and switch to a function view.

Categories

Resources