Django / postgres - maintain a table of counters - django

I want to maintain a running counter per user, that will be given to the user items.
User table will have a item_counter column
class Item:
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
item_num = models.IntegerField() # should be populated from the user table
The question, when the user is creating an item, what is the best way to increment the counter and to copy the new counter to the newly created item, atomically?
Technically I atomically need to:
Increment and get the counter of the user
Create a new Item with the value for item_counter
What is the django / postgres way to do that?
EDIT - example:
Users table:
| username | item_counter |
| john | 20 |
Now, the user sends POST /item. The result will be:
Items table:
| name | item_num |
| my_item | 21 |
Users table:
| username | item_counter |
| john | 21 |
Of course, there can be many users. Each user has an item_counter of its own.

From what I understand you need to assign a special integer to your newly created item record whose value will be fetched from a user's counter variable. The user's counter variable will increment every time the user creates a new Item record.
So as per your challenge this is your solution.
In your class Item declare a method for incrementing the value of counters
from django.db import transaction, IntegrityError #Django inbuilt atomicity transaction block
class Item(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
item_num = models.IntegerField()
def increment_counter(self):
temp = self #Here I have used temp so that it does not throw an out of reference error in your atomic block below
with transaction.atomic():
# This code executes inside a transaction.
temp.user.counter = temp.user.counter + 1
temp.user.save()
temp.item_num = temp.user.counter
temp.save()
In your views.py POST method once you saved/created the new Item you need to call the item.increment_counter() method
newly_created_item = Item.objects.create(....some data)
try:
newly_created_item.increment_counter()
except IntegrityError:
# Handle any exception here

Related

How to get count of subjects taken by each student?

I've two models 'Students' and 'Enrollments'.
The schema for these is as below:
class Students(models.Model):
id = models.AutoField(primary_key=True, unique=True)
name = models.CharField()
class Enrollments(models.Model):
enroll_id = models.AutoField(primary_key=True, unique=True)
student_id = models.ForeignKey(Students, on_delete=models.CASCADE)
subjects = models.charField()
I'm trying to achieve the result of following SQL query in Django Rest Framework, for getting number of subjects enrolled by students (individually).
select
s.id, s.name, count(e.subjects) as count
from Students as s
left outer join Enrollments as e
on e.student_id_id = s.id
group by s.id, s.name, e.subjects
order by count asc;
This query returns result like:
---------------------------
| id | name | count |
---------------------------
| 1 | a | 1 |
| 2 | b | 0 |
| 3 | c | 2 |
---------------------------
Can anyone please help me acheive this kind of result.
Note: I need 0 count students details also.
What you can do is when you are creating a serializer, you can add a serializer method field which will get the count for you.
Add this at the top of your serializer:
count = serializers.SerializerMethodField('get_count')
Then add a function inside your serializer like this:
def get_count(self, obj):
try:
return Enrollments.objects.filter(student_id=obj.id).count()
except:
return None
Finally, add 'count' to your field list. You can then add as many fields as you want. I hope this will get you your desired result. Also don't forget to use "select_related" in the ORM inside your view to reduce the amount of queries.

Render data in a table on a Django template from multiple tables after filtering

I am new to Django and any help on my query is appreciated. I am trying to create an inventory portal and want to display summary information in the template as a table.
models.py
class Location(models.Model):
location_name = models.CharField(max_length=5)
....< other attributes >
class Rack(models.Model):
location = models.ForeignKey(Location, on_delete=models.SET_NULL)
rack_id = models.CharField(max_length=10)
....< other attributes >
class Equipment(models.Model):
rack_id = models.ForeignKey(Rack,on_delete=models.SET_NULL)
make = models.CharField(max_length=20)
model = models.CharField(max_length=20)
type = models.CharField(max_length=20)
....< other attributes >
My query
There are several equipments installed on a rack (such as Comms equip, Processing equip, etc.,) which in turn is available on a location. I want to render summary information onto a template where the first column should be location_name, the second column the count of comms equipment in that location, and the third column count of processing equip, and so on.
| Location | Comms Equip | Processing Equip |
| -------- | ----------- | ---------------- |
| Loc1 | 5 | 8 |
| Loc2 | 2 | 10 |
I can obtain the first column by:
views.py
location = Location.objects.all()
context = {'location': location, }
return render(request, 'appname/summary.html', context)
summary.html
{% for x in location %}
{{ x.location_name }}
For second and subsequent columns, I need to filter with a particular location ID which I am unable to obtain with the context available in the views. Hence for the first column it is not with any specific location ID but for rest of the columns, it is with a specific ID (such as count of comms equip with location ID 1) which is where I am struggling. Aplogize if this question is naive but I tried to search for this but was unable to understand any response which answers my question. Thanks.

DJANGO - Using simple history, how can I show both a base model record and a related history group calculation in one query?

I am using the simple history library for my Django project. It's pretty nifty, but I'm having trouble showing aggregated history stats next to a base model object.
Here's what my model looks like:
from django.db import models
from django.contrib.auth.models import User
from simple_history.models import HistoricalRecords
class RepairForm(models.Model):
user_id = models.ForeignKey(User, on_delete=models.DO_NOTHING,)
return_number = models.CharField(max_length=200, unique=True)
status_id = models.ForeignKey(RFormStatus, on_delete=models.DO_NOTHING)
...
history = HistoricalRecords()
def __str__(self):
return self.return_number
The Docs lead me to believe the proper way of accessing historical records is using the history manager. I can get both sets of information I want:
All Forms (base model objects) -
RepairForm.objects.all()
User ID | Return Number | Status ID
-----------------------------------------------------------
33 | 0a6e6ef0-a444-4b63-bd93-ae55fe8a3cee | 65001
44 | 5f699795-5119-4dcd-8b94-34f7056e732c | 65002
...
A history calculation (history object)
In this example I am getting the latest event of each form -
RepairForm.history.all()\
.values('return_number').annotate(latest_event_date=Max('history_date'))\
.order_by('return_number')
Return Number | latest_event_date
-----------------------------------------------------------
0a6e6ef0-a444-4b63-bd93-ae55fe8a3cee | 7/27/2018
5f699795-5119-4dcd-8b94-34f7056e732c | 8/1/2018
...
I feel like this should be possible to do in one query though no? One query that outputs something like this:
User ID | Return Number | Status ID | latest_event_date
------------------------------------------------------------------------------
33 | 0a6e6ef0-a444-4b63-bd93-ae55fe8a3cee | 65001 | 7/27/2018
44 | 5f699795-5119-4dcd-8b94-34f7056e732c | 65002 | 8/1/2018
...
you can add a property for example "latest_event_date" that calculates the wanted result.then it is always calculated when you run a query on RepairForm!
#property
def latest_event_date(self):
....

Django join on multiple foreign fields (left join)

I'm using django 1.10 and have the following two models
class Post(models.Model):
title = models.CharField(max_length=500)
text = models.TextField()
class UserPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
approved = models.BooleanField(default=False)
How do I get a list of all the posts including the 'approved' property for the logged in user if exists? So instead of multiple queries, it would be one left join query, pseudo-code:
select * from posts as p
left join user_posts as up
on up.post_id = p.post_id
and up.user_id = 2
Output
post_id | title | text | user_id | approved
1 | 'abc' | 'abc' | 2 | true
2 | 'xyz' | 'xyz' | null | null
3 | 'foo' | 'bar' | 2 | true
I created the models this way because the 'approved' property belongs to the user. Every user can approve/reject a post. The same post could be approved and rejected by other users. Should the models be setup differently?
Thanks
Update:
I'm trying to create a webpage to display all available posts and highlight the ones that the current user approved. I could just list all posts and then for each post check if the 'UserPost' table has a value, if yes get the approved property else ignore. But that means if I have 100 posts I'm making 100 + 1 calls to the db. Is it possible to do 1 call using ORM? If this is not possible, should the models be setup differently?
Then I think you need something like this:
Post.objects.all().annotate(
approved=models.Case(
models.When(userpost_set__user_id=2,
then=models.F('userpost__approved')),
default=models.Value(False),
output_field=models.BooleanField()
)
)

django accessing raw many to many created table fields

Model:
class Subjects (models.Model):
name = models.CharField(max_length=100)
places = models.CharField(max_length=100)
class Student (models.Model):
name = models.CharField(max_length=40)
lastname = models.CharField(max_length=80)
subjects = models.ManyToManyField(Subjects, blank=True)
Django creates appname_student_subjects when I use model above.
appname_student_subjects table looks for example, like this:
id | student_id | subjects_id
-----------------------------------------
1 | 1 | 10
2 | 4 | 11
3 | 4 | 19
4 | 5 | 10
...
~1000
How can I access subjects_id field and count how many times subjects_id exists in the table above (and then do something with it). For example: If subject with id 10 exists two times the template displays 2. I know that I should use "len" with result but i don't know how to access subject_id field.
With foreign keys I'm doing it like this in a for loop:
results_all = Students.objects.filter(subject_id='10')
result = len(results_all)
and I pass result to the template and display it within a for loop but it's not a foreign key so it's not working.
You can access the through table directly.
num = (Students.subjects # M2M Manager
.through # subjects_students through table
.objects # through table manager
.filter(student_id=10) # your query against through table
.count())