Referencing Variables in Another Django Model with Foreign Key - python

I have a Project Model where a user can add a Project with an associated Position(s) for the Project. As an example, Website would be project whereas web developer would be the Position. Here are two models.
class Project(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='project')
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.TextField()
complete = models.BooleanField(default=False)
def __str__(self):
return self.title.title()
class Position(models.Model):
project = models.ForeignKey(Project, default='',related_name='positions')
name = models.CharField(max_length=140)
description = models.TextField()
skill = models.ForeignKey(Skill, default='')
filled = models.BooleanField(default=False)
def __str__(self):
return '{} - {}'.format(self.project.title.title(), self.name.title())
I have a view created to show the user's profile and any current or past projects worked on. See below:
class ProfileView(LoginRequiredMixin,generic.TemplateView):
template_name = 'accounts/profile.html'
login_url = settings.LOGIN_REDIRECT_URL
def get_context_data(self, **kwargs):
context = super(ProfileView, self).get_context_data(**kwargs)
lookup = kwargs.get('username')
user = models.User.objects.get(username=lookup)
profile = models.UserProfile.objects.prefetch_related('skills').get(user=user)
context['profile'] = profile
context['skills'] = [skill for skill in profile.skills.all()]
projects = models.Project.objects.all()
context['current_projects'] = projects.filter(Q(owner=user) & Q(complete=False))
context['past_projects'] = projects.filter(Q(owner=user) & Q(complete=True))
return context
I'm having trouble figuring out how to reference the position(s) for a particular projects in my html template. I know that if i try in python shell, i can query the position class and get all objects and then grab the project variables from there.
I tried to create a position 'context' in the view like this:
positions = m.Position.objects.all()
context['positions'] = positions.filter(Q(owner=user)& Q(complete=False))
But Django doesn't like that 'owner' variable--which i understand since i'm just grabbing data from positions. I know in the shell i can do something like m=Position.objects.all() and then do a m[0].project.title to get the project data. For some reason i just can't understand how to put it all together in the code. Any help is greatly appreciated! Been racking my brain on this one for a while!

To traverse related objects, you can use the lowercased name of the model followed by __ (2 underscores) and the field name in the other model.
So instead of this:
positions.filter(Q(owner=user)& Q(complete=False))
Write like this:
positions.filter(Q(project__owner=user) & Q(project__complete=False))

Related

Why is VSCode intellisense type hints different for these 2 Django model objects?

In the views.py after importing the Video model I am testing how intellisense works in VScode.
This is the views.py
from django.shortcuts import render
from .models import Video
# Create your views here.
video1: Video = Video.objects.get(id=1)
video2: Video = Video.objects.filter(id=1).first()
this is the models.py:
class Video(models.Model):
'''
info about videos
'''
video_id = models.TextField(blank=True, null=True)
title = models.TextField()
description = models.TextField()
tags = models.TextField()
is_uploaded = models.BooleanField()
date_uploaded = models.DateField(null=True)
filename = models.TextField()
thumbnail_filename = models.TextField()
When I start typing I get this for video1 which is from video1: Video = Video.objects.get(id=1):
As you can see it offers model fields
but for video2 which is from video2: Video = Video.objects.filter(id=1).first():
it doesn't offer model fields.
Why is that and how can we fix it?
The first one knows that it is getting a single model instance as it is guaranteed by the QuerySet. I think the second one is not guaranteed to return a model instance.
In the django source code for this:
def first(self):
"""Return the first object of a query or None if no match is found."""
for obj in (self if self.ordered else self.order_by('pk'))[:1]:
return obj
so it is returning Optional[<instance>] whereas get() returns an instance.
Interestingly, earliest may work for your usecase, and it is guaranteed to return a model instance as it subcalls get.

DJANGO get objects in sql like join

Context: I'm forcing my self to learn django, I already wrote a small php based website, so I'm basically porting over the pages and functions to learn how django works.
I have 2 models
from django.db import models
class Site(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Combo(models.Model):
username = models.CharField(max_length=50)
password = models.CharField(max_length=50)
dead = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
siteID = models.ForeignKey(Site, on_delete=models.PROTECT)
class Meta:
unique_together = ('username','siteID')
def __str__(self):
return f"{self.username}:{self.password}#{self.siteID.name}"
When creating a view, I want to get the Combo objects, but I want to sort them first by site name, then username.
I tried to create the view, but get errors about what fields I can order by Cannot resolve keyword 'Site' into field. Choices are: dead, id, password, siteID, siteID_id, timestamp, username
def current(request):
current = Combo.objects.filter(dead=False).order_by('Site__name','username')
return render(request, 'passwords/current.html',{'current':current})
Since I'm not necissarily entering the sites into the database in alphabetical order, ordering by siteID wouldn't be useful. Looking for some help to figure out how to return back the list of Combo objects ordered by the Site name object then the username.
You can order this by siteID__name:
def current(request):
current = Combo.objects.filter(dead=False).order_by('siteID__name','username')
return render(request, 'passwords/current.html',{'current':current})
since that is the name of the ForeignKey. But that being said, normally ForeignKeys are not given names that end with an ID, since Django already adds an _id suffix at the end for the database field.
Normally one uses:
class Combo(models.Model):
# …
site = models.ForeignKey(Site, on_delete=models.PROTECT)
if you want to give the database column a different name, you can specify that with the db_column=… parameter [Django-doc]:
class Combo(models.Model):
# …
site = models.ForeignKey(
Site,
on_delete=models.PROTECT,
db_column='siteID'
)

Connect and present data from two different tables in django

I'm trying to easily present data from two different tables (classes). I have an Environment class with all the environments details and a Changes class which contain history changes on all my environments.
My view is currently showing all my Environment details. I want to add to this view the last change been made on each environment (e.g last modified by: User).
My models.py look like this:
class System(models.Model):
system_name = models.CharField(max_length=40, blank=True)
system_id = models.CharField(max_length=100, blank=True)
system_clusters = models.ManyToManyField(Cluster, blank=True)
system_owner = models.CharField(max_length=20, blank=True)
def __str__(self):
return self.system_name
class Changes(models.Model):
date = models.DateTimeField(auto_now_add=True)
cluster = models.ForeignKey(System, on_delete=models.CASCADE)
user = models.CharField(max_length=20, blank=True)
change_reason = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.date
At first, i though to pass a dictionary to my template with the system as a key and a change as a value:
last_changes = {}
change = Changes.objects.filter(cluster__in=s.system_clusters.all()).order_by('-id')[0]
last_changes[s.system_id] = change.change_reason
Even though it partially works (I still trying to parse the dict in my template), I feel like this is not the right approach for the task.
I'm hoping to reach a result where I can just call system.last_change in my template. Can I add another field for System class that will point to his last_change in the Changes table?
You can write a method on System to return the last change for an item:
def last_change(self):
return self.changes_set.order_by('-date').first()
Now you can indeed call system.last_change in the template.

How to use a queryset filter with M2M models in django admin-export?

I need to export some data in Django Admin, I'm using the django-import-export lib and the export is fine, the only problem is that it gets the ID of the attribute instead of his name(of course)
Given the models, on the Operation export, I want the book atribute to get all the book's title instead of the ID's tied with the operation
class Book(models.Model):
title = models.CharField()
value = models.FloatField()
class Operation(models.Model):
book = models.ManyToManyField(Book, through='BookOperation')
sender = models.CharField()
date = models.DateField()
class BookOperation(models.Model):
book = models.ForeignKey(Book)
operation = models.ForeignKey(Operation)
quantity = models.IntegerField()
Here is the OperationResource in admin.py
class OperationResource(resources.ModelResource):
book = fields.Field(column_name='Book(s)', widget=ManyToManyWidget(Book))
class Meta(object):
model = Operation
exclude = ('id',)
def dehydrate_book(self, operation):
return operation.book.get()
With the code like this, i'm getting
get() returned more than one Book -- it returned 2!
I tried using Operation.book.filter but doesn't see to work, i must be doing something wrong !

Update Table Relationship in Django Admin

I'm trying to create a directory of sites, I'm new in Django. What I need is: one site can have many payment processors and one payment processors (Paypal, Payza, etc) can belong to many sites. I'm trying to create a table relationship to represents this. My models are like this:
# Models.py
class Sites(models.Model):
name = models.CharField(max_length=75)
link = models.CharField(max_length=150)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class PaymentProcessors(models.Model):
name = models.CharField(max_length=75)
def __str__(self):
return self.name
class Sites_PaymentProcessors(models.Model):
site = models.ManyToMany(Sites)
payment_processor = models.ManyToMany(PaymentProcessors)
First, I'd like to know if my models are right. If not, how can I fix it?
Second, I'm using Django Admin site to create the sites and payment processors, how can I populate automatically my Sites_PaymentProcessors table with the relation between Sites and Payment_Processors when I add a new Site?
I would slightly change the models to accomodate ManyToManyFields like this:
class Sites(models.Model):
name = models.CharField(max_length=75)
link = models.CharField(max_length=150)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class PaymentProcessors(models.Model):
name = models.CharField(max_length=75)
sites = models.ManyToManyField('Sites', related_name='payment_processors')
def __str__(self):
return self.name
Now, if you want custom fields or store more information along with the relationship, you can make use of the through table
For example, if you want to associate the amount limit or something more custom:
class Sites(models.Model):
name = models.CharField(max_length=75)
link = models.CharField(max_length=150)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class PaymentProcessors(models.Model):
name = models.CharField(max_length=75)
sites = models.ManyToManyField('Sites', related_name='payment_processors', through='SitePaymentProcessor')
def __str__(self):
return self.name
from django.core.validators import MaxValueValidator
class SitePaymentProcessor(models.Model):
site = models.ForeignKey('Site')
payment_processors = models.ForeignKey('PaymentProcessors')
amount_limit = models.IntegerField(default=1000,
validators=[
MaxValueValidator(100)
])
Now, again this is just an example.
Now, registering the admin classes would enable you to populate data into the models via the admin interface.
To auto-populate a large dataset, I would consider using fixtures rather than populating elements individually.

Categories