Using Relationship Based Data in Model Definition - Django - python

I want to make the string representation of a field show data based on a JOIN, for instance:
For Penciler - I want the string representation to resolve to
John Doe (DC) - But the publisher value in that class is a Foreign Key - How do I reference the publisherName?
from django.db import models
class Series(models.Model):
seriesId = models.AutoField(primary_key=True)
series_name = models.CharField(max_length=300)
publisher = models.ForeignKey('Publisher', on_delete = models.PROTECT)
first_published = models.DateField()
last_published = models.DateField()
discontinued = models.BooleanField(default=False)
def __str__(self):
return f'{self.series_name} - {self.publisher} ({self.first_published - self.last_published})'
class Meta:
ordering = ['publication_year','title']
class Publisher(models.Model):
publisherId = models.AutoField(primary_key=True)
publisherName = models.CharField(max_length=250, null=False)
def __self__(self):
return self.publisherName
class Penciler(models.Model):
pencilerID = models.AutoField(primary_key=True)
pencilerName = models.CharField(max_length=200)
publisher = models.ForeignKey('Publisher', on_delete= models.PROTECT)
def __str__(self):
return self.pencilerName (self.publisher)

You can access the related Publisher instance through the ForeignKey field and get the publisherName in the __str__() method so:
def __str__(self):
publisher_name = self.publisher.publisherName
return f'{self.pencilerName} ({publisher_name})'
Additionally, I'd recommend you to use string formatting, such as using f-strings.

It is as simple as that:
def __str__(self):
return f"{self.pencilerName} ({self.publisher})"

Related

Import into tables from Django import_export

I am struggling to populate models in Django by using ForeignKey. Let's say we have as in import_export documentation the following example:
class Author(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Category(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField('Book name', max_length=100)
author = models.ForeignKey(Author, blank=True, null=True, )
...
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True)
def __str__(self):
return self.name
How can I implement import_export module that can check if there is an existing author by name (not by id), that is not case sensitive, and that can generate a new author if it does not exist?
As an example, let's say the CSV file looks like:
name,author,...,price,categories
J.R.R. Tolkien,Lord of the Rings,...,40,["cat1","cat2"]
Also, if there is a DateTime field, how to generate that in ForeignKey table?
NOTE: I know about use of natural key:
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
class AuthorManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
class Author(models.Model):
objects = AuthorManager()
name = models.CharField(max_length=100)
birthday = models.DateTimeField(auto_now_add=True)
def natural_key(self):
return (self.name,)
# Only the author field uses natural foreign keys.
class BookResource(resources.ModelResource):
author = Field(
column_name = "author",
attribute = "author",
widget = ForeignKeyWidget(Author, use_natural_foreign_keys=True)
)
class Meta:
model = Book
But I am not sure how to check for UPPER or lower case in the CSV. And how to generate a new Author if it does not exist.
There are a couple of ways of creating an FK relation during import if it does not already exist.
Option 1 - override the before_import_row() method
class BookResource(resources.ModelResource):
# note use of 'iexact' for case-insensitive lookup
def before_import_row(self, row, **kwargs):
author_name = row["author"]
Author.objects.get_or_create(name__iexact=author_name,
defaults={"name": author_name})
# other code omitted
Option 2 - subclass ForeignKeyWidget
Simply subclass ForeignKeyWidget and implement the check in clean():
class AuthorForeignKeyWidget(widgets.ForeignKeyWidget):
def clean(self, value, row=None, **kwargs):
author, created = Author.objects.get_or_create(name__iexact=value,
defaults={"name": value})
return author
class BookResource(resources.ModelResource):
author = fields.Field(
column_name='author',
attribute='author',
widget=AuthorForeignKeyWidget(Author))
# other code omitted
Either way will work fine. I would personally use option 2.
Also, if there is a DateTime field, how to generate that in ForeignKey table?
Since you are calling Author.objects.get_or_create() you can add a date if you wish, for example:
author, created = Author.objects.get_or_create(name__iexact=value,
defaults={"name": value, "created": timezone.now()})
If using natural keys you can adjust the code as desired.
Related answer about creating in bulk mode

How can I set foreign key from post request in Django

I have this models
class Driver(models.Model):
first_name = models.CharField(max_length=250)
last_name = models.CharField(max_length=250)
created_at = models.DateTimeField(default=NOW)
updated_at = models.DateTimeField(default=NOW)
def __str__(self):
return self.first_name
class Vehicle(models.Model):
driver_id = models.ForeignKey(Driver,on_delete=SET_NULL,unique=True,null=True, blank=True)
make = models.CharField(max_length=150)
model = models.CharField(max_length=150)
plate_number = models.CharField(max_length=10,validators = [validate_plate_numberLATIN,validate_plate_numberCYRYLLIC], unique=True)
created_at = models.DateTimeField(default=NOW)
updated_at = models.DateTimeField(default=NOW)
def __str__(self):
return self.make
I try to set foreign key in my post request into Vehicle model
#method_decorator(csrf_exempt, name='dispatch')
def post(self,request,*args, **kwargs):
body = json.loads(request.body.decode("utf-8"))
newCar = Vehicle.objects.create(driver_id=body['driver_id'],make=body['make'],model=body['model'],plate_number=body['plate_number'])
data = json.loads(serializers.serialize('json',[newCar]))
return JsonResponse({'success':data})
And get this error
ValueError: Cannot assign "1": "Vehicle.driver_id" must be a "Driver" instance.
How to get rid off this error? How I can create an instance of Driver and 'post' an id?
You can do it in 2 ways
If you need the driver instance in somewhere in the code you can use this
driver_instance = Driver.objects.get(pk=body['driver_id'])
Vehicle.objects.create(driver_id=driver_instance,..)
Vehicle.objects.create(driver_id_id=body['driver_id'], ...)
The raw value of a ForeignKey can be accessed by appending "_id" to the field name, this can also be used to create an instance using the raw value
Vehicle.objects.create(driver_id_id=body['driver_id'], ...)

How to show foreignkey attributes django admin fields?

This question is similar with others but it is a different one actually ! So, I have 3 models such as (I have deleted some unnecessary things for shorter code):
class Category(models.Model):
category_title = models.CharField(max_length=200)
category_content = models.TextField()
category_slug = models.CharField(max_length=200)
def __str__(self):
return self.category_title
class Classes(models.Model):
classes_title = models.CharField(max_length=200)
classes_content = models.TextField()
classes_category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT)
def __str__(self):
return self.classes_title
class Subjects(models.Model):
subject_title = models.CharField(max_length=200)
subject_content = models.TextField()
subject_class = models.ForeignKey(Classes, on_delete=models.SET_DEFAULT)
def __str__(self):
return self.subject_title
So let me give an example. I can have 2 categories and in those categories I can have "same named" classes. Lets think about maths is a class for both categories. When I want to add a new subject to maths I see 2 same named maths in admin page. So I want to know which one belongs to which category in admin page. So I can add my subject to right class.
class SubjectAdmin(admin.ModelAdmin):
fields = ('subject_title', 'subject_content', 'subject_class',)
So in this picture (Subjects = Konular) I am adding a new subject. I will have a foreign key to Class. However I have same name for classes that are coming from different categories. So in this dropdown how can I know which class belongs to which category ?
Try this...
class KonularAdmin(admin.ModelAdmin):
fields = ('subject_title', 'subject_content', 'subject_class', 'get_classes_category_title')
def get_classes_category_title(self, obj):
subject_object = Subjects.objects.get(id=obj.subject_class)
return str(subject_object.classes_category.category_title)
It returns the category title name
If I understood you correctly, This should work.
class Classes(models.Model):
classes_title = models.CharField(max_length=200)
classes_content = models.TextField()
classes_category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT)
def __str__(self):
return self.title
class Subjects(models.Model):
subject_title = models.CharField(max_length=200)
subject_content = models.TextField()
subject_class = models.ForeignKey(Classes, on_delete=models.SET_DEFAULT)
def __str__(self):
return f"{self.subject_title} - {str(self.subject_class)}"
You can use __str__() method to change the string representation of an object:
class Subjects(models.Model):
subject_title = models.CharField(max_length=200)
subject_content = models.TextField()
subject_class = models.ForeignKey(Classes, on_delete=models.SET_DEFAULT)
def __str__(self):
return f"{self.subject_title} - {self.subject_content} - {self.subject_class}"
# shorter version:
# return f"{self.subject_title[:10]} - {self.subject_content[:10]} - {self.subject_class[:10]}"
Check it with:
>>> print(Subjects.objects.first())

ValueError: Cannot assign "<Truckdb: Truckdb object (1)>": "Quiz.truck_name" must be a "truck_name" instance

I am trying to create an instance in my app like this:
Views.py
new_quiz = Quiz.objects.create(owner=request.user, comments="Autogenerated", truck_type=truck_type_object,
truck_name=chosen_truck_object)
where chosen_truck_object is this:
chosen_truck_object = Truckdb.objects.filter(display_name=chosentruck)[0]
And Models.py
class Quiz(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quizzes')
comments = models.TextField(max_length=256, blank=True)
truck_type = models.ForeignKey(truck_type, on_delete=models.CASCADE, related_name='trucks')
truck_name = models.ForeignKey(truck_name, on_delete=models.SET_NULL, null=True)
class truck_type(models.Model):
name = models.CharField(max_length=30)
color = models.CharField(max_length=7, default='#007bff')
def __str__ (self):
return self.name
class truck_name(models.Model):
truck_type = models.ForeignKey(truck_type, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
def __str__ (self):
return self.name
How can I pass the truck_type and truck_name instance to the Quiz model in Quiz.objects.create ?
Firstly you need to follow the naming convention guidelines, so your models' name must be camelcase doc as like:
class TruckType(models.Model):
name = models.CharField(max_length=30)
color = models.CharField(max_length=7, default='#007bff')
def __str__ (self):
return self.name
class TruckName(models.Model):
truck_type = models.ForeignKey(TruckType, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
def __str__ (self):
return self.name
And then please migrate your database and then for your problem you need to take TruckName object instead of Truckdb.
chosen_truck_object = TruckName.objects.filter(display_name=chosentruck)[0]
instead of filter use get method chosen_truck_object = TruckName.objects.get(display_name=chosentruck) it will save

Django Queryset with annotate

I am writing one method in Django Manager model.
I want to write method that finds out number of all sold copies (books) per author.
I have two models and method written in Manager.
My problem is that method should also be chainable from any Author queryset, for example something like
Author.objects.filter(...).exlucde(...).total_copies_sold()
should also work.
Example:
author = Author.objects.create(...)
Book.objects.create(..., author=author, copies_sold=10)
Book.objects.create(..., author=author, copies_sold=20)
author_total_books = Author.objects.total_copies_sold().first()
>>> author_total_books.copies
30
Below my code. It works like in example above, but then I try something like:
author_books = Author.objects.filter(id=2).total_copies_sold()
I got
'QuerySet' object has no attribute 'annotate'
class AuthorManager(models.Manager):
def total_copies_sold(self):
return self.get_queryset().annotate(copies=Sum('book__copies_sold')
class Author(models.Model):
first_name = models.CharField(max_length=120)
last_name = models.CharField(max_length=120)
objects = AuthorManager()
class Book(models.Model):
title = models.CharField(max_length=120)
copies_sold = models.PositiveIntegerField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
[Edited]
Thank you schillingt for reply. I added:
class AuthorQueryset(models.QuerySet):
def total_copies_sold(self):
return self.annotate(copies=Sum('books__copies_sold'))
I tried something like:
author_books = Author.objects.filter(id=2).total_copies_sold()
>>> author_books.copies
I got
'AuthorQueryset' object has no attribute 'copies'
What you are lookig for is :
from django.db import models
from django.db.models import Sum
from django.db.models.functions import Coalesce
class AuthorManager(models.Manager):
def get_queryset(self):
return AuthorQuerySet(self.model, using=self._db)
def annotate_with_copies_sold(self):
return self.get_queryset().annotate_with_copies_sold()
class AuthorQuerySet(models.QuerySet):
def annotate_with_copies_sold(self):
return self.annotate(copies_sold=Sum('books__copies_sold'))
class Author(models.Model):
objects = AuthorManager()
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Book(models.Model):
title = models.CharField(max_length=30)
copies_sold = models.PositiveIntegerField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
Now it is possible to chain queries e.g.:
author_total_books = Author.objects.total_copies_sold().first()
However you will no be able to use it on QuerySet object like:
author_books = Author.objects.filter(id=2).total_copies_sold()
That is because you are annotating Author object, not a QuerySet. To obtain that result you should execute:
Author.objects.annotate_with_copies_sold().get(id=2)
author.copies_sold
15
You need to use Manager.from_queryset to set your manager. Here are the docs.
class AuthorQueryset(models.QuerySet):
def total_copies_sold(self):
...
class Author(models.Model):
objects = models.Manager.from_queryset(AuthorQueryset)()
from django.db import models
from django.db.models import Sum
from django.db.models.functions import Coalesce
class AuthorManager(models.Manager):
def get_queryset(self):
return AuthorQuerySet(self.model, using=self._db)
def annotate_with_copies_sold(self):
return self.get_queryset().annotate_with_copies_sold()
class AuthorQuerySet(models.QuerySet):
def annotate_with_copies_sold(self):
# Write your solution here
return self.annotate(copies_sold=Coalesce(Sum('books__copies_sold'), 0))
class Author(models.Model):
# Make sure this manager is available.
objects = AuthorManager()
# objects = models.Manager.from_queryset(AuthorQuerySet)()
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Book(models.Model):
title = models.CharField(max_length=30)
copies_sold = models.PositiveIntegerField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
enter code here
class AuthorManager(models.Manager):
def get_queryset(self):
return AuthorQuerySet(self.model, using=self._db)
def annotate_with_copies_sold(self):
return self.get_queryset().annotate_with_copies_sold()
class AuthorQuerySet(models.QuerySet):
def annotate_with_copies_sold(self):
return self.annotate(copies_sold=Sum('Book_Author__copies_sold'))
class Author(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
objects = AuthorManager()
class Meta:
verbose_name_plural = "Author"
verbose_name = 'Author'
ordering = ('first_name','last_name')
class Book(models.Model):
title = models.CharField(max_length=250, unique=True)
copies_sold = models.PositiveIntegerField(default=0)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='Book_Author')
class Meta:
verbose_name_plural = "Book"
verbose_name = 'Book'
ordering = ('title','copies_sold')
from vehicles.models import Author, Book
try:
author = Author.objects.get(first_name='Mark', last_name='Twain')
except Author.DoesNotExist:
author = Author.objects.create(first_name='Mark', last_name='Twain')
try:
book = Book.objects.get(author=author, title='Adventrure of Huckleberry Finn', copies_sold=7)
except Book.DoesNotExist:
book = Book.objects.create(author=author, title='Adventrure of Huckleberry Finn', copies_sold=7)
pass
try:
book = Book.objects.get(author=author, title='Adventrure of Tomm Saywer', copies_sold=4)
except Book.DoesNotExist:
book = Book.objects.create(author=author, title='Adventrure of Tomm Saywer', copies_sold=4)
pass
author = Author.objects.annotate_with_copies_sold().first()
print(author.copies_sold)
11
1.Create AuthorManager, AuthorQuerySet classes from Author and Books
2.Create Author, Book models
3.Prepare Test Data and use model manager to filter the queryset
Count books sold by authors using Django ORM
Wihtout writing custom manager you can use "AuthorQueryset" in Author Manager
Just a menthion AuthorQueryset.as_manager() in Author models, that's it.
Here are the Django Docs
from django.db import models
from django.db.models import Sum, Value
from django.db.models.functions import Coalesce
# Create your models here.
class AuthorQuerySet(models.QuerySet):
def annotate_with_copies_sold(self):
return self.annotate(copies_sold=Coalesce(Sum('books__copies_sold'),Value(0)))
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
objects = AuthorQuerySet.as_manager()
class Book(models.Model):
title = models.CharField(max_length=30)
copies_sold = models.PositiveIntegerField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")

Categories

Resources