Multiple foreign key lookups - python

I have the following models in my app
Account
class Account(CommonModel): # Accounts received from Client
client = models.ForeignKey('Client', on_delete=models.RESTRICT)
reference = models.CharField(db_index=True, max_length=50)
def __str__(self):
return f"{self.client} {self.reference}"
Person
class Person(CommonModel):
title = models.CharField(max_length=100,choices=choi.person_title())
name = models.CharField(db_index=True, max_length=100)
birth_date = models.DateField()
def __str__(self):
return f"{self.title} {self.name}"
AccountPerson
class AccountPerson(CommonModel): # Account -> Person link
account = models.ForeignKey("core.Account", on_delete=models.RESTRICT, related_name="accountperson_account")
person = models.ForeignKey("core.Person", on_delete=models.RESTRICT, related_name="accountperson_person")
contact_type = models.CharField(max_length=50, choices=choi.contact_type())
def __str__(self):
return f"{self.account} - {self.person} ({self.contact_type})"
The AccountPerson model holds relationships between accounts and people (one person can have multiple accounts). I'm trying to return a query set containing a list of Accounts, and the Person they're linked to (if any). My background is SQL, so I'm thinking of a query that would hit Account -> AccountPerson --> Person, but I'm stuck.
I've tried prefetch_related() but I'm only returning details in the Account table - I'm unsure of how to access Person from there and put those fields into my HTML file.
View
def account_list(request):
data = Account.objects.all().prefetch_related('accountperson_account')
return render(request, 'core/account_list.html', {'data': data})
account_list.html
Code condensed for readability
...
{% for i in data %}
<tr>
<td>{{i.client}}</td>
<td>{{i.reference}}</td>
{% endfor %}
...
I'm currently in a position where my page loads, and I see the entries in my Account model, but that's it.
Update
I changed my view to this
def account_list(request):
data = AccountPerson.objects.all().select_related('account').select_related('person')
return render(request, 'core/account_list.html', {'data': data})
And I can now access fields in Account and Person in my HTML like so
{% for i in data %}
<tr>
<td>{{i.account.client}}</td>
<td>{{i.account.reference}}</td>
<td>{{i.contact_type}}</td>
<td>{{i.person.name}}</td>
{% endfor %}
I just want to check that this is the right way (or one of them)?

I'd change the datamodel slightly to be more Django-y. Django has the concept of ManyToMany fields which is what you're trying to accomplish. (https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.ManyToManyField)
You would define the Person model as you did and change the Account model to have a ManyToMany field (you could also switch it around, that won't matter).
You can also defined the intermediate model like you intended. Use the through argument on the ManyToMany for this.
You can use Related Manager to handle all lookups both ways: account.person and person.account (the 'account' part is set by the related_name).
class Account(CommonModel): # Accounts received from Client
client = models.ForeignKey('Client', on_delete=models.RESTRICT)
reference = models.CharField(db_index=True, max_length=50)
person = models.ManyToManyField(Person, through=AccountPerson, related_name='account')

Related

Show django filter options according to the logged-in user

I’m new to Django and am trying to show filter options that are only relevant to the user - for example, my app has Message and Automation classes with a many:many relationship, but my filter shows a select option with automations created by other users rather than only those created by the logged-in user.
How can I get it to only show those created by the current user?
The view:
#login_required(login_url='login')
#allowed_users(allowed_roles=['admin', 'customer'], own_account_only=True)
def message_list(request, pk):
account = Account.objects.get(id=pk)
messages = account.message_set.all()
filter = MessageFilter(request.GET, queryset=messages)
messages = filter.qs
context = {'account': account,
'messages': messages, 'filter': filter}
return render(request, 'messages/message_list.html', context)
The filter:
class MessageFilter(django_filters.FilterSet):
class Meta:
model = Message # model we’re building filter for
fields = '__all__'
exclude = ['account', 'date_created', 'text', 'subject']
The classes:
class Message(models.Model):
name = models.CharField(max_length=100)
subject = models.CharField(max_length=128)
text = models.TextField()
account = models.ForeignKey(Account, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True, null=True)
automations = models.ManyToManyField('automations.Automation', blank=True)
def __str__(self):
return self.name
class Automation(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=200)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True, null=True)
messages = models.ManyToManyField(Message, blank=True)
def __str__(self):
return self.name
And the HTML:
<form method="get" class="filter-form with-ps hide">
{{filter.form.as_p}}
<button class="button" type="submit">Search</button>
</form>
I’m assuming I need to edit the messages passed into MessageFilter to somehow exclude automations owned by all users, or somehow tell MessageFilter to only show automations etc of the current logged-in user?
Thank you.
Note: the image below shows the messages listed in the table (correct as they're owned by the logged-in user) and an automations filter option which incorrectly includes some automations not owned by the user (e.g. test automation 3)
One way would be to restrict the queryset to exclude messages that the logged-in user should not see
messages = account.message_set.exclude( something = whatever)
whatever will be be based on request.user. However, that will completely restrict what the user can see, not merely the initial default of what the user can see.
Another way is to "doctor" request.GET for initializing the filter. Instead of
filter = MessageFilter(request.GET, queryset=messages)
do
request_get = request.GET.copy()
# mutate request_get based on contents of request.GET and request.user
...
filter = MessageFilter(request_get, queryset=messages)
request_get is mutable, unlike request.GET. You can inspect it to see what if any filters the user has requested. On the first view there won't be any, and you could insert some appropriate ones by assiging appropriate key/value pairs in
request_get. This way the user can override the initial defaults you choose for him.

When to use __str___ method inside Django model?

I have created a Django model with the following attributes.
class Info(models.Model):
number = models.IntegerField()
ID = models.IntegerField()
reading = models.IntegerField()
date = models.DateField()
I would like to make it so that when a user searches for an 'ID' or 'number' from the database, they are shown the date and reading. Here is my search results code in views.py:
class SearchResultsView(ListView):
model = Info
template_name = 'search_results.html'
def get_queryset(self):
query = self.request.GET.get('q')
reading_list = Info.objects.filter(
Q(ID__icontains=query) | Q(number__icontains=query)
)
return reading_list
And here is my search_results.html template:
<h1>Search Results</h1>
<ul>
{% for reading in reading_list %}
<li>
{{ reading.reading }}, {{ reading.date }}
</li>
{% endfor %}
</ul>
I am a little confused as to whether I should include a 'str' method in my model. Will the app be able to print the date and reading using just this code?
Based on your current settings, there is no need to add a __str__ function in your model because you are using instance's fields rather than instance itself.
However, if there are any references to the instance itself, e.g. a foreign key to this model or you just want to check the instance itself, adding a __str__ function will increase the readability. You can check the __str__ documentation for details.
Without __str__ field, if you have an Info instance, you will have:
<Info: Info object (1)>. After adding a __str___ function and return str(id) for example, you will see <Info: 1>.
It would be great to have a CharField(e.g. description = models.CharField()) in your Info model if you want to add the __str__ function. Then the representation of this object would be <Info: Good Reading>
Whenever an instance of model is created in Django, it displays the object as ModelName Object(1).to make changes to your Django model using this
def __str__(self):
return str(self.id) #if its integer, make it str
return self.name #if its already str. you dont make it str
it changes the display name from ModelName Object(1) to field name of def __str__(self): in your admin panel.
and one thing def __str__(self) for python 3 and def __unicode__(self): for python 2

Django view only returning incomplete data, only one field to template

I have forked the django-oscar catalogue app to alter the models being used. Not in a major way, and not in a way that would affect pulling data from the database as far as I can see. This seems to be supported by the fact the the django-oscar dashboard still works fine and lets me add and view products. My models.py from my forked app:
from django.db import models
class Collection(models.Model):
name = models.CharField(max_length=50)
prod_category = models.CharField(max_length=50)
description = models.TextField()
manufacturer = models.TextField()
num_products = models.PositiveIntegerField()
image_url = models.URLField()
from oscar.apps.catalogue.abstract_models import AbstractProduct
class Product(AbstractProduct):
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, null=True)
multiplier = models.DecimalField(max_digits=2, decimal_places=1, default='2.2')
from oscar.apps.catalogue.models import *
Here is my relevant view from my views.py
def product(request):
template = loader.get_template('/home/my_app/my_site/main_page/templates/main_page/product.html')
prods = Product.objects.values_list('categories')
context={'prods': prods}
return HttpResponse(template.render(context))
I tried loading from the built in model and my forked model (commenting and uncommenting one or both), neither makes a difference:
#from forkedoscarapps.catalogue.models import Product
from oscar.core.loading import get_class, get_model
Product = get_model('catalogue', 'product')
And the code I am using in the template to display data from the view:
{% for instance in prods %}
<li><{{ instance.name }}</li>
{% endfor %}
The resulting HTML is:
<li></li>
Which shows it is reaching the for loop, but for some reason no data is returned.
There is at least one category called beds, which displays fine in the django-oscar dashboard. What have I missed in my view?
edit: When I change instance.name to just instance I get the following returned in the HTML:
(1,)
So it is somewhat working, and showing what I assume is the primary key being returned, but why is the name of the field not being returned?
Product.objects.values_list('categories') yields a list of id tuples that represent the categories associated with the products in that queryset. That's not what you want to send to the template, you want to send instances, more specifically product instances if I'm not mistaken.
Do Product.objects.all() instead, and just use {{ instance.title }} in the template according to the definition of the oscar model: https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/catalogue/abstract_models.py and to what ever you customised over it.

Passing objects primary key from its view to its ManyToMany-relationship object view

I don't know if the title is clear. It's quite difficult for me to explain it in simple way because the problem is not that simple and I'm not a very good English speaker. Every suggestion for improving a title are welcome.
So, let's go to the actual problem... In my application people can join different groups. Currently, I'm creating invitation system for this purpose, so user can send an invitation to another user. In a group view, I have a list of users which are not connected with this group, which allowing group members to invite those people. So, when I'm going to the group view, I am passing it's Primary Key. To create database cell about the invitation I need a group's PK and users PK as well (I want to do it using another view, but I'm not sure it's the best solution).
My question is: How can I pass those two PKs to the new view where I will create group-user relation cell in a database?
models.py:
class Group(models.Model):
name = models.CharField(max_length=500)
members = models.ManyToManyField(User, blank=True, related_name="member")
invitations = models.ManyToManyField(User, blank=True, related_name="invitations")
views.py:
def not_yet_members_list_view(request, pk):
group = Group.objects.get(pk=pk)
not_yet_members = User.objects.all() #doesn't matter
args = {'group': group, 'not_yet_members': not_yet_members}
return render(request, 'groups/show_members.html', args)
# Here is my problem (this code is a simplified version of what I want to achieve)
def invite_user_view(request, group_pk, user_pk):
invite_user(group_pk, user_pk)
return render(request, 'groups/show_members.html')
urls.py:
url(r'^not_yet_members_list/(?P<pk>\d+)/$', views.not_yet_members_list, name='not_yet_members_list'),
template.html:
{% for user in not_yet_members %}
<!-- ??? -->{{ user }}Invite
{% endfor %}
in the view from where you need to pass the group and user
request.session['group_pk'] = group_pk
request.session['user_pk']= user_pk
in the view where you need to access the data
group_pk = request.session['group_pk']
user_pk = request.session['user_pk']
then
invite_user(group_pk, user_pk)
after successful sending invitation
del request.session['group_pk']
del request.session['user_pk']
request.session.modified = True

Django Apply Model Foreign Key Default Value to Existing Record

Thank you very much for taking your time.
Previously, I posted this question, How to Get Unread Posts for Users.
The problem was: I cannot filter out which article one user has not read while this article has already been read by another user.
I figured out why I cannot do that---- because I have data that I wrote into the database without using Django although I set the reading default for each post to False---- there is simply no record.
Then, I manually set one article to unread to a user in the Admin, everything works, because now in the database there is one record stating that this certain article has not been read by this user.
The problem now is:
How do I set "unread" for every existing article that I have in the database for all existing users?
And how can articles stay unread for every new user unless the new user actually read it?
For your convenience, I copied the codes to here.
My model:
class Posts(models.Model):
title = models.CharField(max_length=255)
content_url = models.URLField(unique=True)
content = models.CharField(max_length=255)
post_date = models.DateField(default="2999-12-12")
Another
class readstatus(models.Model):
reading_status = models.BooleanField(default=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
article = models.ForeignKey(Posts, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
My View:
class DailyReading(ListView):
template_name = 'reading.html'
context_object_name = "data"
paginate_by = 20
def get_queryset(self):
if self.kwargs['status'] == "read":
queryset = piaoyou.objects.filter(readstatus__reading_status=True,readstatus__user=self.request.user)
return queryset
else:
queryset= Posts.objects.filter(readstatus__reading_status=False,readstatus__user=self.request.user)
return queryset
My Template:
{% for info in data %}
<quoteblock>
<ul>
<li><a href="{{ info.get_absolute_url }}">{{ info.title }}
<footnote></footnote>
</a>{{ info.post_date }}</li>
<footnote>{{ info.get_abstract_content }}</footnote>
</ul>
</quoteblock>
{% endfor %}
OMG, I just figured out how to do this.
For an article that has been read by the requesting user, we can just pass queryset = Post.objects.filter(readstatus__reading_status=True, readstatus__user=self.request.user)
For an article that has not been read by the requesting user yet, we can pass Posts.objects.all() into the template.
Then in the template:
We need {% if instance.readstatus_set.all %} this line to make things work. Assume there is no data about whether this requesting user has read the article, the instance should not have readstatus_set.all, which means the user has not read the article yet. Once checking this if condition, we can carry out to check other conditions in the loop.

Categories

Resources