Django modelling question:
I'm writing a manufacturing e-commerce site. We have an inventory of all components and we want to define blueprints for products that we could build using those components. How could I model that?
Say for example I have a class AtomicComponent
class AtomicComponent(models.Model):
component = models.CharField(max_length=255)
length = models.IntegerField(blank=True, null=True)
thickness = models.IntegerField(blank=True, null=True)
colour = component = models.CharField(max_length=255)
quantity = models.IntegerField(blank=True, null=True)
+----+------------+--------+-----------+--------+----------+
| id | Component | length | thickness | colour | quantity |
+----+------------+--------+-----------+--------+----------+
| 45 | table-legs | 80 | 3 | white | 90 |
| 46 | table-tops | 100 | 3 | white | 25 |
| 47 | bolts | 1 | null | null | 3000 |
+----+------------+--------+-----------+--------+----------+
How could I specify blueprints like:
ikea-table: 4x(id=45) + 1x(id=46) + 4x(id=47)
Trying to find a way so that the product could have a relationship with specific objects so that when a customer tries to order a table, it would check if all the components are available.
You should create another model to store each component of the blueprint:
class Blueprint(models.Model):
name = models.CharField(max_length=250)
atomic_component = models.ForeignField(AtomicComponent, on_delete=models.PROTECT, related_name='components')
amount = models.PositiveIntegerField(default=1)
This model with store a record for every component in the blueprint, with the amount of them on field ´amount´.
Related
There is a Django filter problem with ManyToManyField that bothers me.
I have a task that I want the creator and selected members of the creator to view. Now I have a problem that the specified member can be viewed normally, but when the creator views it, there will be the same number of duplicate tasks as the specified member. I can't understand this problem, it is not what I expected.
#views
class TaskView(LoginRequiredMixin, View):
def get(self, request):
projecttask_all = ProjectTask.objects.filter(Q(owner=request.user.username) |
Q(task_member=request.user))
print(projecttask_all)
#print results
<QuerySet [<ProjectTask: user1>, <ProjectTask: user1>]>
// My understanding should be like <QuerySet [<ProjectTask: user1>]>, because the owner is not in projecttask_task_member,but it is not.
#model
class ProjectTask(models.Model):
title = models.CharField(max_length=100, verbose_name='title', default='')
owner = models.CharField(max_length=30, verbose_name='owner')
task_member = models.ManyToManyField('UserProfile',related_name='task_member', blank=True, null=True)
#mysql
projecttask
| id | title | owner |
| -- | ----- | ----- |
| 1 | test | user1 |
projecttask_task_member
| id | projecttask_id | userprofile_id |
| -- | -------------- | -------------- |
| 1 | 1 | 8 |
| 2 | 1 | 9 |
I'd like to output a table using "Premises" model in Django admin. In addition I'd like include the output in this table of an additional column, say "last utility payment". It is actually a column in a related table. There may be no payment in the database so admin should able to see either an empty cell or the date of payment.
I was able to write a DB query that displays the information I need. Its significant and worked part is given below:
SELECT jp.id,
jp.number apartment,
jp.building_number building,
jp.rent,
jp.arrears,
jpm.last_payment
FROM jasmin_premises jp
LEFT JOIN (
SELECT pm.premises_id,
max(pm.paid) last_payment
FROM jasmin_payment pm
GROUP BY pm.premises_id
) jpm ON jp.id = jpm.premises_id;
And the output is similar to the following:
id | apartment | building | rent | arrears | last_payment
--------------------------------------------------------------
170 | 1X | 6-A | 297.43 | 2.57, | NULL
72 | 2 | 4 | 289.66 | -678.38 | 2021-01-31
173 | 3Z | 7 | 432.86 | 515.72 | 2021-02-04
73 | 4 | 8-B | 292.25 | 515.44 | 2021-02-04
74 | 5 | 8-B | 112.42 | 3249.34 | NULL
75 | 6A | 122 | 328.48 | 386.23 | 2021-02-04
76 | 7 | 42 | 482.06 | 964.12 | 2021-01-31
77 | 8 | 1 | 433.71 | 867.42 | 2021-01-31
78 | 9C | 12 | 322.79 | 322.79 | 2021-02-04
79 | 10 | 122 | 324.22 | 0 | 2021-02-04
80 | 12 | 12 | 322.79 | 1232.46 | NULL
81 | 14 | 5-Z | 440.82 | 978.44 | 2021-02-04
And I'm using the following models (only a significant part):
class Premises(models.Model):
number = models.CharField(
blank=False,
null=False,
max_length=10)
building_number = models.CharField(
blank=False,
null=False,
max_length=3)
rent = models.DecimalField(
blank=False,
null=False,
max_digits=12,
decimal_places=2,
default=0.0)
area = models.DecimalField(
blank=False,
null=False,
max_digits=5,
decimal_places=2)
class Payment(models.Model):
paid = models.DateField(
blank=False,
null=False)
premises = models.ForeignKey(
Premises,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name='payments',
db_index=True)
Is there a way to override admin.ModelAdmin.get_queryset (for example using annotations) to get an extra column like in my example above? Is there any other way to make a LEFT JOIN on a compound DB query using Django ORM?
to make this query in django you have to add the models.Manager() to the tables like this:
models.py
class Premises(models.Model):
# existent code
objects = models.Manager()
class Payment(models.Model):
# existent code
objects = models.Manager()
In the part of app you want to access this information
from .models import Premises, Payment
premises = Premises.objects.all()
data_to_display = []
for premise in premises:
payments = Payment.objects.filter(premises=premise).order_by('-paid')
if len(payments) == 0:
last_payment = "Null"
else:
last_payment = payments[0]
object_to_list = {
"id": premise.id,
"apartment": premise.number,
"building": premise.building_number,
"rent": premise.rent,
"arreaars": premise.area,
"last_payment": last_payment.paid
}
data_to_display.append(object_to_list)
The solution is to add an explicit subquery to a QuerySet using the Subquery expression. We'll need also to use OuterRef because a queryset in a Subquery needs to refer to a field from the outer query.
So let's create a subquery:
from django.db.models import OuterRef
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
The next step is to pass payments subquery to a queryset:
from django.db.models import Subquery
# 'payments' here is from example above
premises = Premises.objects.annotate(
last_payment=Subquery(payments.values('paid')[:1])
)
Finally, lets see the used SQL to query objects rows the database:
print(premises.query)
(the output is formatted, only the significant part is shown)
SELECT "jasmin_premises"."id",
"jasmin_premises"."number",
"jasmin_premises"."building_number",
"jasmin_premises"."arrears",
"jasmin_premises"."rent",
(SELECT U0."paid"
FROM "jasmin_payment" U0
WHERE U0."premises_id" = "jasmin_premises"."id"
ORDER BY U0."paid" DESC
LIMIT 1) AS "last_payment"
FROM "jasmin_premises";
Now, after performing the tests, we can use this in our ModelAdmin:
from django.contrib import admin
from django.db.models import OuterRef, Subquery
from .models import Payment, Premises
#admin.register(Premises)
class PremisesAdmin(admin.ModelAdmin):
list_display = (
'number',
'building_number',
'rent',
'arrears',
'last_payment',
)
def get_queryset(self, request):
qs = super().get_queryset(request)
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
qs = qs.annotate(
last_payment=Subquery(payments.values('paid')[:1]),
)
return qs
def last_payment(self, obj):
return obj.last_payment
last_payment.short_description = 'Last payment'
last_payment.admin_order_field = 'last_payment'
Well, this doesn't use JOINs, but this approach will force Django to execute a subquery.
Probably in some cases it may be possible to write an equivalent queryset that performs the same task more clearly or efficiently however, this is the best I have achieved so far.
I am working on a Django project where I need to link one table(model) to each user.
Assume MyTable_1 maps to user_1 and so on.
The primary key for MyTable will be a DateField which contains continuous dates from the time user signed-up.
MyTable_1 for User_1
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 10/01/20 | rice | chess | Yes |
|-----------|----------|-------------|-----------------|
| 11/01/20 |sandwhich | tennis | No |
|-----------|----------|-------------|-----------------|
MyTable_2 for User_2
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 16/03/19 | pizza | rugby | Yes |
|-----------|----------|-------------|-----------------|
| 17/03/19 | pasta | football | Yes |
|-----------|----------|-------------|-----------------|
And so on for every new user created. User logs in those information in MyTable.
How can I implement this? I am using PostgreSQL and have written custom User Model.
You really don't need seperate tables just seperate rows.
A ForeignKey relation will do the trick, something like this in your models.py:
# user model:
User(models.Model, ...):
first_name = models.CharField(...)
last_name = models.CharField(...)
...
# log model:
Log(models.Model):
user = models.ForeignKey(User, ...)
date = models.DateField(...)
food_ate = models.CharField(...)
game_played = models.CharField(...)
ran_today = models.CharField(...)
class Meta: unique_together = ('user', 'date',)
Then, elsewhere, you can access your users' logs like so:
user = User.objects.get(id='the_user_id')
logs = user.logs.all()
Consider the following models:
class Publisher(models.Model):
name = models.CharField(max_length=300)
num_awards = models.IntegerField()
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
publisher = models.ForeignKey(Publisher, related_name='related_books')
From a Publisher instance how can I get the number of book by distinct value on pages field? For example:
| name | pages | publisher |
|-----------|-------|-----------|
| Golden | 20 | 1 |
| Grey | 23 | 1 |
| Blue | 20 | 1 |
| Grotesque | 27 | 2 |
If I have publisher = Publisher.objects.get(id=1) how can I achieve something like this:
# equals to 2 [Golden, Grey]
publisher.related_books.all().distinct('pages').count()
You were close, you just need to restrict returned values, like so:
publisher.related_books.all().values('pages').distinct('pages').count()
This will just give you the number of different page lengths for a publisher, but not the associated books for each page length. To do that you'd probably need an extra query.
If you want reusable queries, you could do this:
class BookQuerySet(models.QuerySet):
def by_publisher(self, publisher):
return self.filter(publisher=publisher)
def distinct_number_of_pages(self):
return self.distinct(pages)
class Book(...):
...
objects = BookQuerySet.as_manager()
class Publisher(...):
#property
def number_of_page_lengths(self):
return Book.objects.by_publisher(self).distinct_number_of_pages().count()
I have two models that look like this:
class Node(models.Model):
user = models.ForeignKey(User, null=False)
name = models.CharField(max_length=50)
class Activation(models.Model):
node = models.ForeignKey(Node, null=False)
active = models.BooleanField(default=False)
datetime = models.DateTimeField(default=datetimeM.datetime.now)
The activation table stores whether a given node is "active" or not. So to figure out whether a node is active, one needs to get the latest activation record for that node.
I'm trying to figure out how to write a django query that returns all active nodes.
Here is some example data
Node Table
id | name
--------------------
0 | andrew
1 | bill
2 | bob
Activation Table
id | nodeId | active | datetime
--------------------
0 | 0 | false | 01-01-2013:00:01:02
1 | 0 | true | 01-02-2013:00:01:02
2 | 0 | false | 01-03-2013:00:01:02
3 | 1 | false | 01-04-2013:00:01:02
4 | 0 | true | 01-05-2013:00:01:02
5 | 1 | true | 01-06-2013:00:01:02
6 | 2 | false | 01-07-2013:00:01:02
So the query would need to return [node0, node1]
class Node(models.Model):
user = models.ForeignKey(User, null=False)
name = models.CharField(max_length=50)
class Activation(models.Model):
node = models.ForeignKey(Node, null=False, related_name='activations')
active = models.BooleanField(default=False)
datetime = models.DateTimeField(default=datetimeM.datetime.now)
#latest activation record for that node:
try:
latest_activation = node.activations.latest('id')
except:
latest_activation = None
# Return all active notes:
all_active_notes = Node.objects.filter(activations__active=True)
Updated:
Check this question what I posted yestarday:
Django reverse query by the last created object
maybe this will help you.