Django Admin - Unable to change Foreign Key field - python

I have Django version 1.4.5
Here are the relevant parts of my model
class Product (models.Model):
name=models.CharField(max_length=200)
description=models.TextField()
label=models.ForeignKey('Label')
pub_date = models.DateTimeField(editable=False)
def save(self):
#item will not have id if this is the first save
if not self.id:
self.pub_date = datetime.date.today()
super(Product, self).save()
def __unicode__(self):
return self.name
class Label(models.Model):
"""
A clothing label, e.g. Kate Spade
"""
name=models.CharField(max_length=100)
def __unicode__(self):
return self.name
When I attempt to publish a Product, selecting a Label works fine. Publishing the item works as expected, and the label field remains populated upon returning to the Product in the admin console. However, if I attempt to change the value of the label field, I am taken to the default list of Products page, with the message "he product "Prod 1" was changed successfully" but returning to the Prod 1 page reveals that the field wasn't actually saved properly.
Any ideas here?

super(Product, self).save() is within the if block, so it isn't being called on edits. Also, why not just use auto_now_add on the pub_date field?

In your case, no need to set the date & time explicitly. You can use 'auto_now_add' Please fer this link for more details.
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateField.auto_now_add
class Product (models.Model):
name=models.CharField(max_length=200)
description=models.TextField()
label=models.ForeignKey('Label')
pub_date = models.DateTimeField(editable=False, auto_now_add = True)
def __unicode__(self):
return self.name
If you need to set it manually, Use the following snippet. It calls super class for change also.
def save(self):
#item will not have id if this is the first save
if not self.id:
self.pub_date = datetime.date.today()
super(Product, self).save()

Related

Python 3 Django Rest Framework - how to add a custom manager to this M-1-M model structure?

I have these models:
Organisation
Student
Course
Enrollment
A Student belongs to an Organisation
A Student can enrol on 1 or more courses
So an Enrollment record basically consists of a given Course and a given Student
from django.db import models
from model_utils.models import TimeStampedModel
class Organisation(TimeStampedModel):
objects = models.Manager()
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(TimeStampedModel):
objects = models.Manager()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
organisation = models.ForeignKey(to=Organisation, on_delete=models.SET_NULL, default=None, null=True)
def __str__(self):
return self.email
class Course(TimeStampedModel):
objects = models.Manager()
language = models.CharField(max_length=30)
level = models.CharField(max_length=2)
def __str__(self):
return self.language + ' ' + self.level
class Meta:
unique_together = ("language", "level")
class EnrollmentManager(models.Manager):
def org_students_enrolled(self, organisation):
return self.filter(student__organisation__name=organisation).all()
class Enrollment(TimeStampedModel):
objects = EnrollmentManager()
course = models.ForeignKey(to=Course, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
student = models.ForeignKey(to=Student, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
enrolled = models.DateTimeField()
last_booking = models.DateTimeField()
credits_total = models.SmallIntegerField(default=10)
credits_balance = models.DecimalField(max_digits=5, decimal_places=2)
Notice the custom EnrollmentManager that allows me to find all students who are enrolled from a given organisation.
How can I add a custom Manager to retrieve all the courses from a given organisation whose students are enrolled?
What I have tried
I thought to create a CourseManager and somehow query/filter from that side of the relationship:
class CourseManager(models.Manager):
def org_courses_enrolled(self, organisation):
return self.filter(enrollment__student__organisation__name=organisation).all()
This works, but it gives me the same 100 enrollment records :(
What I am trying to get is:
based on a given organisation
find all students who are enrolled
and then (DISTINCT?) to get the list of enrolled courses for that org
This is the view:
class OrganisationCoursesView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = CourseSerializer
queryset = Course.objects.get_courses(1)
and the url:
# The below should allow: /api/v1/organisations/1/courses/
router.register('api/v1/organisations/(?P<organisation_pk>\d+)/courses', OrganisationCoursesView, 'organisation courses')
UPDATE 1
Based on the answer from h1dd3n I tried this next:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(student__organisation_id=organisation)
but that throws an error (as I expected it would):
FieldError: Cannot resolve keyword 'student' into field. Choices are:
courses, created, id, language, level, modified, progress
UPDATE 2 - getting closer!
Ok with help from #AKX's comments:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(courses__student__organisation_id=organisation)
now DOES return courses, but it returns a copy for each enrolled student. So now I need to group them so each record only appears one time...
First you need to change self.filter to self.get_queryset().filter() or make a seperate method in the manager.
def get_queryset(self):
return super(CourseManager, self).get_queryset()
In manager create a function
def get_courses(self,organisation):
return self.get_queryset.filter(student__oraganisation=organisation)
This should return the students and you don't need to call .all() - the filtered qs either way returns you all the objects that it finds.
EDIT
Try this:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter( \
enrollments__student__organisation_id=organisation).distinct()
UPDATE 2
You can try and play around with from django.db.models import Q https://docs.djangoproject.com/en/3.0/topics/db/queries/
or with annotate based on this answer Get distinct values of Queryset by field where you filter out each student so it would appear once.

Displaying the value of ManyToMany relationship in django admin

I am trying to display a brand of a bike in my django admin panel. I have managed to display the title, but I am struggling with the brand.
Here's my models.py:
class Bike(models.Model):
item = models.OneToOneField(Item, on_delete=models.CASCADE)
category = models.ManyToManyField(Category, blank=True)
image = models.ImageField(upload_to='bikes')
brand = models.ManyToManyField(Brand, null=True)
def __str__(self):
return self.item.title
class Brand(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
I have tried that:
def __str__(self):
return self.brand.name
But nothing is displaying then. Any ideas how to display the self.item.title and brand name at the same time?
Try this instead and see if it works.
`def brand_names(self):
return ', '.join([x.name for x in self.brand.all()])`
You need to return brand name in str
So I give you stuff apply that
def ___str__(self):
return ",".join([brand.name for brand in self.brand.objects.all()])
Above stuff give all the brand name in your admin panel

Refer related model field in model layer

I am try to refer 'spot_price' of model 'Spot' in model 'Basis' in django model layer, How can I manage this?
I have designed view.py to automaticaly render the templates. so I am not able to modifty any view.py to choose data like 'models.B.objects.get().field'.
and more, str is set to indicate the date, so, in the django backstage admin, the 'spot' field display would be 'date' formate, how would be change to 'spot_price'?
model Spot
class Spot(models.Model):
date = models.DateField(primary_key=True)
spot_price = models.FloatField(blank=True, null=True)
def __str__(self):
return str(self.date) if self.date else ''
need to refer the model Spot'spot_price by date, cause date is unique but spot_price is not
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(default=calculate_basis)
def __str__(self):
return str(self.date) if self.date else ''
def calculate_basis(self):
return abs(self.major_future_contract_close_price -
self.spot.spot_price)
I expect the Basis.query.data would to like 'date: 2019-04-25, major_future_contract_close_price: 100.0, spot: 96.5, basis: 3.5'
You can't use class method as default, because it requires self, which is not existing when you are still creating the object.
If you need to have it stored in field (database), override default save() method or use signals to modify the basis field once your object is created. Also note that you have to recalculate basis every time close_price or spot_price changes, as the value is just written in database.
Probably better solution would be to use #property so for anyone that will be using you model it will look like a field, but it will dynamically calculate value based on current data.
For example, I'm overriding save() to calculate the basis field. I set it as editable=False, which means it won't appear in forms by default (and you will not be able to see it in admin!). You can safely remove that part if you want to just look at the values.
Additionally I add basis_as_property property.
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(editable=False, blank=True)
#property
def basis_as_property(self):
return '%s' % (self.calculate_basis())
def __str__(self):
return str(self.date) if self.date else ''
def save(self, *args, **kwargs):
if not self.basis:
self.basis = self.calculate_basis()
super(Basis, self).save(*args, **kwargs)
def calculate_basis(self):
return abs(self.major_future_contract_close_price - self.spot.spot_price)
As for Spot str repr, I don't think it's possible to change it based on where it is referenced. If you want to use spot_price, you can just use: return str(self.spot_price) if self.spot_price else str(self.date)

Django signals with ForeignKey Fields

I have created a knowledge structure that has "blocks"and each block has children to cater for different situations.
The code is:
models.py
class KBSBlock(models.Model):
name = models.CharField(max_length=150, unique=True)
code = models.CharField(max_length=4, blank=True)
status=models.CharField(max_length=1, choices=Status_Choices, default='Draft')
enter_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.PROTECT)
tags = TaggableManager(blank=True)
attribute1 = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if self.code is None or self.code == "":
self.code = create_code4(self)
super(KBSBlock, self).save(*args, **kwargs)
#receiver(post_save, sender=KBSBlock)
def create_block(sender, instance, created, **kwargs):
if created:
#create_block = BlockDetails.objects.create(block_dts=instance)
print('Working!')
class BlockDetails(models.Model):
block_dts = models.ForeignKey('KBSBlock', on_delete=models.CASCADE)
code = models.CharField(max_length=2, blank=True)
attribute1 = models.CharField(max_length=100, default='All')
created_at = models.DateTimeField(auto_now_add=True)
enter_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.PROTECT)
status=models.CharField(max_length=1, choices=Status_Choices, default='Draft')
Whenever I create a block, I want to create a generic detail in BlockDetails for the block with (code='00', attribute1='All', enter_by='request.user')
It prints the 'working' bit with the 'create_block' line hashed out.
I am using PostgreSQL, Django 2.1 and Python 3.7, and cannot get it right.
Help Please
First of all thanks to #Dani Herrera and #Davit Tovmasyan! Between the two of them I figured out what the problem was: Turns out I had a few things wrong.
The error was coming from the database: value too long for type character varying(1) telling me that I was trying to enter a string that was too long for the intended field. This field was the status field - It appears that even though the choices option worked perfectly in normal circumstances, the signal command wanted the short form of the choice only.
The correct code is as follows:
#receiver(post_save, sender=KBSBlock)
def create_block(sender, instance, created, **kwargs):
if created:
instance.blockdetails_set.create(block_dts=instance.name, code='00', enter_by=instance.enter_by, attribute1='All', status='D')
NB: the case of the modelname MUST be lowercase, even in the model class has uppercase letters
Once I corrected that - everything worked.

how can we query foreign key and get results of objects which are connected to foreign key

How can I can use managers to query my foreign key and then retrieve objects that are connected to foreign key?
Here is my models.py:
from django.db import models
# Create your models here.
class BookManager(models.Manager):
def title_count(self,keyword):
return self.filter(title__icontains=keyword).count()
class CategoryManager(models.Manager):
def category_count(self):
return self.filter(category__icontains=python).count()
class Category(models.Model):
title=models.CharField(max_length=20)
def __str__(self):
return self.title
class Enquiry(models.Model):
title=models.CharField(max_length=200)
category=models.ForeignKey(Category ,default=False,blank=False)
detail=models.TextField()
objects = BookManager()
objects=CategoryManager()
# tags=models.ChoiceField()
def __str__(self):
return self.title
I tried to use category manager but it gave me a strange error.
I just want to know how exactly we can get the objects that are connected with category foriegn-key and show them as list to the users.
You can combine both the title_count and category_count methods under one Manager. When filtering through a ForeignKey, your field lookup needs to specify the name of the field you're trying to filter on, else it will assume you're querying the ID field. So instead of category__icontains, you would do category__title__icontains.
Another note, in newer versions of Django, you are required to specify the on_delete parameter when defining a ForeignKey.
This is a working example of what I think you're trying to accomplish.
class BookManager(models.Manager):
def title_count(self, keyword):
return self.filter(title__icontains=keyword).count()
def category_count(self, keyword):
return self.filter(category__title__icontains=keyword).count()
class Category(models.Model):
title = models.CharField(max_length=20)
def __str__(self):
return self.title
class Enquiry(models.Model):
title = models.CharField(max_length=200)
category = models.ForeignKey(Category,
default=False,
blank=False,
on_delete=models.CASCADE)
detail = models.TextField()
books = BookManager()
# tags=models.ChoiceField()
def __str__(self):
return self.title
Here's how you would use it:
Enquiry.books.category_count('python')
Enquiry.books.title_count('test')

Categories