I'm really stuck in how to do this and appreciate the help. I have three models, two of them shares the same foreign key field:
class AccountsPlan (models.Model):
code = models.CharField(max_length=7, unique=True,)
name = models.CharField(max_length=100, unique=True,)
class Planning (models.Model):
accountplan = models.ForeignKey(AccountsPlan, on_delete=models.PROTECT)
month = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2,)
class Revenue (models.Model):
accountplan = models.ForeignKey(AccountsPlan, on_delete=models.PROTECT)
receipt_date = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2,)
And I have this view that annotates the sum for each model by the foreign key name with a form that filters by date:
def proj_planning(request):
form = ProjectionFilterForm(request.POST or None)
# some definitions for the form
planned = Planning.objects.values('accountplan__name').annotate(Sum('amount')).order_by('accountplan__code').filter(
month__range=[start_date, planned_end_date])
done = Revenue.objects.values('accountplan__name').annotate(Sum('amount')).filter(
receipt_date__range=[start_date, end_date],).order_by('accountplan__code')
if request.method == 'POST':
planned = Planning.objects.values('accountplan__name').annotate(Sum('amount')).order_by(
'accountplan__code').filter(month__range=[start_date, planned_end_date],
accountplan__name__icontains=form['accountplan'].value())
done = Revenue.objects.values('accountplan__name').annotate(
Sum('amount')).filter(receipt_date__range=[start_date, end_date],
accountplan__name__icontains=form['accountplan'].value())
comp_zippedlist = zip(planned, done)
return render(request, 'confi/pages/planning/proj_planning.html', context={
'form': form,
'list': comp_zippedlist,
})
The code kinda works (it doesn't throw any errors), but the thing is, I will never have an exact amount of data for each model.
For example: if I have 6 different records in the Planning model, but only 4 records in the Revenues model, the zipped list will only show the 4 records in the Revenues and not the other 2 records in the Planning model, which makes sense.
But what I need is to show all 6 records from the Planning model with the corresponding 4 records from the Revenue model and the other 2, blank or something like that. Ultimately, I will need to compare the two values and show some kind of status based on the two amount field values, that's why I have to show all of them.
The result I expect is something like this:
Accountplan | Planned | Done
__________________________________
Accountplan A | 500 | 250
Accountplan B | 1000 | 860
Accountplan C | 800 | None
Accountplan D | 150 | 350
Accountplan E | 300 | None
Accountplan F | 700 | 900
I tried to use prefetch_related to do this, using this code:
done = AccountsPlan.objects.prefetch_related('revenue_set').values(
'name').annotate(Sum('revenue__amount')).order_by('code')
And while it shows exactly what I want, if I apply a filter in the receipt_date field of the Revenue model, it will have the same result as before, witch again, make sense.
I thought of looping through every revenue record with no accountplan and apply a temporary value 0 to them, but the accountplan is required, so that idea made no sense.
I also looked at the multi-table inheritance but since the relation with the child models is one-to-one, it does not apply in this case (unless I misread something).
Is there a way to have the behavior described and possible with the prefetch_related code, but keeping all foreign key fiels after a filter? Thanks!
Related
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.
I have CurrencyHistory model along with the database table which is populated on every Currency model update for the historical data.
class CurrencyHistory(models.Model):
id = models.AutoField(primary_key=True)
change_rate_date = models.DateTimeField(_("Change Rate Date"),
auto_now=False, auto_now_add=True,
db_column='change_rate_date')
code = models.ForeignKey("core.Currency", verbose_name=_("Code"),
on_delete=models.CASCADE,
related_name='history',
db_column='code')
to_usd_rate = models.DecimalField(_("To USD Rate"),
max_digits=20,
decimal_places=6,
null=True,
db_column='to_usd_rate')
Database structure looks like
id | change_rate_date | code | to_usd_rate
1 | 2021-01-01 | EUR | 0.123456
2 | 2021-01-01 | CAD | 0.987654
3 | 2021-01-02 | EUR | 0.123459
4 | 2021-01-02 | CAD | 0.987651
I need to fetch data using Djnago ORM to have a dictionary to display single row per date with the every currency as columns, like this
Date
EUR
CAD
2021-01-01
0.123456
0.987654
2021-01-02
0.123459
0.987651
But I have no idea how to correctly do it using Django ORM to make it fast.
I suppose for loop over the all unique database dates to get dict for
each data will work in this case but it looks very slow solution that
will generate thousands of requests.
You want to use serailizers to turn a model instance into a dictionary. Even if your not using a RESTFUL api, serailizers are the best way to get show dictionaries.
All types of ways to serialize data
Convert Django Model object to dict with all of the fields intact
For a quick summary of my top favorite methods..
Method 1.
Model to dict
from django.forms import model_to_dict
instance = CurrencyHistory(...)
dict_instance = model_to_dict(instance)
Method 2:
Serailizers (this will show the most detail)
of course this implies that you'll need to install DRF
pip install rest_framework
Serializers.py
# import your model
from rest_framework import serializers
class CurrencySerialzier(serializers.ModelSerializer):
code = serializers.StringRelatedField() # returns the string repr of the model inst
class Meta:
model = CurrencyHistory
fields = "__all__"
views.py
from .serializers import CurrencySerializer
...inside your view
currency_inst = CurrencyHistory.objects.get()
serializer = CurrencySerializer(currency_inst)
serializer.data # this returns a mapping of the instance (dictionary)
# here is where you either return a context object and template, or return a DRF Response object
You can use the django-pivot module. After you pip install django-pivot:
from django_pivot.pivot import pivot
pivot_table_dictionary = pivot(CurrencyHistory,
'change_rate_date',
'code',
'to_usd_rate')
The default aggregation is Sum which will work fine if you only have one entry per date per currency. If the same currency shows up multiple times on a single date, you'll need to choose what number you want to display. The average of to_usd_rate? The max? You can pass the aggregation function to the pivot call.
Alternatively, put unique_together = [('change_rate_date', 'code')] in the Meta class of your model to ensure there really is only one value for each date, code pair.
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()
)
)
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())
I have a couple of simple objects that have a many-to-many relationship. Django has joined them using obj1_obj2 table and it looks like this in mysql;
id | person_id | nationality_id
-----------------------------------
1 | 1 | 1
2 | 1 | 2
Now when I save obj1 (which shows obj2 in as Multi-select in its form) the ids in the obj1_obj2 table increase even thow I have not changed them. For example I change a basic character field for obj1 on its form and save it and the the data in the joining table appears to be deleted and re-saved giving the entries new ids.
In fact I don't have to change anything all I have to do is save the form and the same thing happens.
All I am doing in the view is form.save(), nothing special. Is that the normal way that it works?
EDIT: Added Models, Views, Forms
class Person(models.Model):
name = models.CharField()
birthdate = models.CharField()
nationality = models.ManyToMany(Nationality)
class Employee(Person):
employeeNum = models.CharField()
class FamilyMember(Person):
employee = models.ForeignKey(Employee)
relationship = models.CharField()
class Nationality(models.Model):
abbrev = models.CharField()
country = models.CharField()
class FamilyMemberDetailsForm(forms.ModelForm):
class Meta:
model = FamilyMemeber
exclude = ['employee']
def editFamilyMember(request, familyMember_id):
familyMember = get_object_404(FamilMember, familyMember_id)
if request.method == 'POST':
form = FamilyMemberDetailsForm(request.POST, instance=familyMember)
if form.is_valid():
form.save()
else:
form = FamilyMemberDetailsForm(instance=familyMember)
return render_to_response(editForm.html, {'form':form},
context_instance(RequestContext(request))
This is a cut down version of the models, but the same thing happens for saving an employee or familyMember. The FamilyMember I have shown because it is as simple as this I create the modelForm and then make changes and then save it. For the employee I do some more manipulation in the init of Form for the Nationality, mainly for presentation, and at first I thought it was this manipulation that was causing it, but as I said the same thing happens with the FamilyMember where I do nothing except save.
The Nationality is presented on the form as a multiselect box with a list and the user can select 1 or more from the list. If I just present the populated form and then save it without changing anything the id for the many-to-many table entry changes.
I have changed the example table titles also.
Thanks,
Andrew
Yes, the deletion of any existing rows in appname_obj1_obj2 is expected behavior when saving a form for an object that has a ManyToManyField.
You can see the clear() before the add(**values) in ReverseManyRelatedObjectsDescriptor and ManyRelatedObjectsDescriptor in django/db/models/fields/related.py.
Pop open a shell and take a look at the queries yourself. Something like this should show you the DELETE before the INSERT in the raw sql.
from django.db import connection
fm = FamilyMember.objects.get(pk=1)
form = FamilyMemberDetailsForm(instance=fm)
data = form.initial
data['name'] = "z%s" % data['name']
form = FamilyMemberDetailsForm(data, instance=fm)
connection.queries = [] # clearing to limit the queries you have to look at
form.save()
for q in connection.queries:
print("%s\n" % q['sql'])