Access to fields of on reverse relation in django - python

I have these models:
class Task(models.Model):
user = models.ForeignKey(User)
name = models.CharField()
class Report(models.Model):
task = models.ForeignKey(
Task, blank=True, null=True, related_name='+')
status = models.CharField(max_length=32, choices=Status.CHOICES, default=Status.INCOMPLETE)
Now I want to get all Tasks and their related status.
How do I do this?

At first, '+' is not a valid related_name. It is also not very explicit.
Try replacing '+' with 'reports' instead:
# ...
class Report(models.Model):
task = models.ForeignKey(
Task,
blank=True,
null=True,
related_name='reports' # <<<
)
status = models.CharField(
max_length=32,
choices=Status.CHOICES,
default=Status.INCOMPLETE
)
Then to get all the Tasks with their related status, you can use values:
>>> Task.objects.values('name', 'report__status')
<QuerySet [{'name': 'test', 'report__status': 'OK'}, ...]>

The sign + is exactly what you don't want to have as related_name in this case. It tells Django to not create a backwards relation. Check out here. Chose another valid name or skip this argument, in which case Django will create a reverse relation by default using for the name of the backwards relation the model name lowercased and the suffix _set (see here for details).
However, in your example if you want to get all reports which are related to a task and the corresponding status you do not necessarily need backwards relationships. Try this:
reports = Report.objects.exclude(task__isnull=True).values('task__name', 'status')

Change the value of related_name to something else, like 'reports' for example:
class Report(models.Model):
task = models.ForeignKey(
Task, blank=True, null=True, related_name='reports')
status = models.CharField(max_length=32, choices=Status.CHOICES, default=Status.INCOMPLETE)
Now if you have a Task object (not queryset), you can get a queryset of it's reports using:
reports = task.reports.all()
You can use filter() on the reports if you need to.
reports = task.reports.filter(status='something')

Related

Django - How to create dependent selects

My task is to implement a form in which the choice of the value of the second field depends on the value of the first field. (For example, if the value of the first field is Cars, then the second field should show sedan/SUV, etc., if the value of the first field is Commercial vehicles, then the second box should show truck/bus, etc.)
Code models.py:
class TypeTransport(models.Model):
transport_name = models.CharField(max_length=100, verbose_name='kind of transport')
class TypeBodyTransport(models.Model):
transport = models.ForeignKey(TypeTransport, on_delete=models.CASCADE, blank=True, null=True,
verbose_name='kind of transport')
body_name = models.CharField(max_length=100, verbose_name='transport body type')
class Advertisement(models.Model):
transport = models.ForeignKey(TypeTransport, on_delete=models.SET_NULL, blank=True, null=True,
verbose_name='kind of transport')
body = models.ForeignKey(TypeBodyTransport, on_delete=models.SET_NULL, blank=True, null=True,
verbose_name='transport body type ')
Code forms.py:
class CreateAdvertisementForm(forms.ModelForm):
transport = forms.ModelChoiceField(queryset=TypeTransport.objects.all(), to_field_name="transport_name")
body = forms.ModelChoiceField(queryset=TypeBodyTransport.objects.filter(transport=transport),
to_field_name="body_name")
class Meta:
model = Advertisement
fields = ('transport', 'body')
I thought it could be done with filter(transport=transport), but this error is returned: TypeError: Field 'id' expected a number but got <django.forms.models.ModelChoiceField object at 0x7f40d7af5ac0>.
Can you please tell me how to implement the feature I need?
have you tried:
class CreateAdvertisementForm(forms.ModelForm):
transport = forms.ModelChoiceField(queryset=TypeTransport.objects.all(), to_field_name="transport_name")
body = forms.ModelChoiceField(queryset=TypeBodyTransport.objects.filter(transport=transport.id),
to_field_name="body_name")
class Meta:
model = Advertisement
fields = ('transport', 'body')
instead of transport = transport, try transport = transport.id
I solved this problem using the django-smart-selects library. Additionally I can say that in the forms it is necessary to remove field which references to the ModelChoiceField, because it interferes with its queryset parameter. I'm still a beginner, so I didn't immediately guess that the problem was in chained selects (I edited the question).

Why is it throwing an Django- Reverse query name SystemCheckError?

I am a newbie to Django, While running the django application using python3 manage.py runserver
Because of the way I have created the model I am getting an error like
django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:
ERRORS:
database.LeadsStatus.company_id: (fields.E305) Reverse query name for 'LeadsStatus.company_id' clashes with reverse query name for 'LeadsStatus.companies'.
HINT: Add or change a related_name argument to the definition for 'LeadsStatus.company_id' or 'LeadsStatus.companies'.
database.Users.id: (fields.E007) Primary keys must not have null=True.
HINT: Set null=False on the field, or remove primary_key=True argument.
System check identified 2 issues (0 silenced).
This is how I've created model.
class LeadsStatus(models.Model):
id = models.IntegerField(primary_key=True, auto_created=True)
company_id = models.ForeignKey('Companies', null=False, db_index=True, on_delete=models.CASCADE)
status = models.CharField(max_length=120)
users_id = models.IntegerField()
assign_date = models.DateTimeField()
companies = models.OneToOneField('Companies', on_delete=models.CASCADE)
I believe the error might be because of the way I have created a OneToOneField. How do I solve this error. Please help me understand. Thanks
If you really need 2 foreing keys to the model Companies, then you need to set the option related_name to distinguish them.
You can change your class to, for example, this:
class LeadsStatus(models.Model):
...
company_id = models.ForeignKey(
'Companies',
null=False,
db_index=True,
on_delete=models.CASCADE,
related_name='leadsstatus_1_set')
...
companies = models.OneToOneField(
'Companies',
on_delete=models.CASCADE,
related_name='leadsstatus')
You could also set related_name on just one of these 2 fields, as long as it is different from the default value of leadsstatus_set.

Having trouble wrapping my head around follower/target models in Models.py

I have just started with making a similar site to Pinterest and the site has follower/target system that I have barely any understanding of. So far, my models.py code is below:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, null=True)
email = models.CharField(max_length=200, null=True)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, null=True)
target = models.ManyToManyField(self, through='Follow')
follower = models.ManyToManyField(self, through='Follow')
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='targets')
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name='followers')
class Meta:
db_table = 'follows'
This code was made with reference to another StackOverflow thread
Django models: database design for user and follower
However, I am having trouble understanding how using "related_name='targets' in 'follower' and "related_name='followers'" in 'target' where I can't see any 'targets'(plural) or 'followers'(plural) in other areas of models.py
Should I get rid of that related_name, since there is no such table called "followers" or "targets"? And if you spot major errors in my code or logic, can you tell me? Thanks!
Should I get rid of that related_name, since there is no such table called followers or targets.
There is never a table named followers or targets. The related_name [Django-doc] is a conceptual relation Django makes to the other model (in this case User). It means that for a User object myuser, you can access the Follow objects that refer to that user through target for example with myuser.followers.all(), so:
Follow.objects.filter(target=myuser)
is equivalent to:
myuser.followers.all()
The default of a related_name is modelname_set, so here that would be follow_set. But if you remove both related_names, then that would result in a name conflict, since one can not add two relations follow_set to the User model (and each having a different semantical value).
if you spot major errors in my code or logic, can you tell me?
The problem is that since ManyToManyFields refer to 'self' (it should be 'self' as string literal), it is ambigous what the "source" and what the target will be, furthermore Django will assume that the relation is symmetrical [Django-doc], which is not the case. You should specify what the source and target foreign keys are, you can do that with the through_fields=… parameter [Django-doc]. It furthermore is better to simply define the related_name of the ManyToManyField in reverse, to avoid duplicated logic.
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, unique=True)
email = models.CharField(max_length=200)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45)
follows = models.ManyToManyField(
'self',
through='Follow',
symmetrical=False,
related_name='followed_by',
through_fields=('follower', 'target')
)
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='targets'
)
target = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='followers'
)
class Meta:
db_table = 'follows'
Here a User object myuser can thus access myuser.follows.all() to access all the users that they follow, myuser.followed_by.all() is the set of Users that follow myuser. myuser.targets.all() is the set of Follow objects that he is following, and myuser.followers.all() is the set of Follow objects that are following that user.

How to Model Peer to Peer Transactions in Django?

I'm trying to figure out the correct way to model transactions between users in an ecommerce site I'm building. I want the transaction to be available to both users in a dashboard.
It seems incorrect to have the transaction, and all its details, saved to both users objects. I think I need to create a separate model for transactions, with a unique id for each transaction. Then, in each user's object I could simply save that transaction id.
Would I then simply give each transaction two primary keys, one for the purchaser's user.id and one for the purchasee's user.id? Is this "many to many"?
Thanks for any guidance here.
Edit - Here is what I've tried:
class Transaction(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
purchaser = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
create_time = models.DateField(auto_now=True)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
However, this gives an error items.Transaction.owner: (fields.E304) Reverse accessor for 'Transaction.owner' clashes with reverse accessor for 'Transaction.purchaser'.
HINT: Add or change a related_name argument to the definition for 'Transaction.owner' or 'Transaction.purchaser'.
You should add related_name parameter for both fields each has its own name, Like :
class Transaction(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name = 'transaction_owner')
purchaser = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name = 'transaction_purchaser')
create_time = models.DateField(auto_now=True)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
More Context : Django: Why do some model fields clash with each other?

Creating user specific form access permissions / validation in django admin

I'm using a 'Task' Model to create operations/administrative tasks in my dashboard. Each task has an assignee, and a reviewer. The assignee completes the task by passing several checks, and the reviewer verifies their work, both of these require each user to edit a check, but neither user should be able to access or modify the other's result.
If the assignee views the Task (with checks inline), they should only be able to modify the "result" and "comment" elements of the check, where as the reviewer can only edit the "review_result" and "reviewer_comment" elements.
To validate this I need to use the fact that given a check, the current user editing the page is equal to check.task.assignee or check.task.reviewer.
I cannot find a simple way to do this, even using django-guardian, as this requires field-level permissions, rather than object level. I considered using modelForm validation, but cannot find a way to access the user from within the model with some hacks such as django-cuser.
Is there another architecture which would allow this? The only way forward that I can see is to use django-guardian combined with two checks, a check and a checkReview, and set object level permissions as the assignee and reviewer are chosen.
class Task(PolymorphicModel):
date_created = models.DateTimeField(auto_created=True)
date_accepted = models.DateTimeField(null=True)
date_reviewed = models.DateTimeField(null=True)
date_closed = models.DateTimeField(null=True)
state = FSMField(default="open")
assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="assigned_tasks",
related_query_name="assigned_task",
)
reviewer = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="review_tasks",
related_query_name="review_task",
)
class Check(PolymorphicModel):
result = models.BooleanField(null=True)
comment = models.CharField(max_length=500, null=True, blank=True)
review_result = models.BooleanField(null=True)
reviewer_comment = models.CharField(max_length=500, null=True)
task = models.ForeignKey(Task, on_delete=models.CASCADE)
The correct method to achieve this is to override the get_readonly_fields method of InlineModelAdmin (In your Inline class).
def get_readonly_fields(self, request, obj=None):
if obj is None:
logger.error("An admin has created a check from the dashboard (this should not happen)!")
return []
user = request.user
fields = [field.name for field in self.opts.local_fields]
if user == obj.assignee:
fields.remove(['result', 'comment'])
elif user == obj.reviewer:
fields.remove(['review_result', 'reviewer_comment'])
return fields

Categories

Resources