Django / How to get the primary key from a specific field? - django

Models.py
class scenes(models.Model):
name = models.CharField('Event name', max_length=120)
record_date = models.DateTimeField('Event date')
Let's say I have recorded a scenes with name="world"
In views.py, how can I query the pk from the name field ?
from .models import scenes
scene = scenes.objects.get('what should I put here to get the pk associated with name World ?')
When I entered :
scene = scenes.objects.get(name='world').pk
I got an error :
UnboundLocalError: local variable 'scenes' referenced before assignment

The easy way to go with that would be to just:
# views.py
from .models import scenes
scene = scenes.objects.get(name="world")
scene_id = scene.pk # This contains the pk of the scene object
Django will try to fetch a unique object with "world" as a value for the name field. With your current code this has an issue:
Your name field is not unique so your DB may contain different scenes objects with the "world" value for the name field. This will lead to problems when calling get. To solve that you could add unique=True when defining the field:
# models.py
class scenes(models.Model):
name = models.CharField('Event name', max_length=120, unique=True)
record_date = models.DateTimeField('Event date')
This will ensure that your DB won't contain objects with the same name.
Note also that if there's no object with name value equal to "world" you'll get an error too. Depending on the context you are using this, you should consider get_object_or_404 or get_or_create

see this: The pk lookup shortcut.
The django documentation declares that primary keys have an equivalent, in this case it is pk. So if you declared a primary key in your model (let's say we called it code) you could access it as code or pk. This is useful both to apply filters (like the link above) or to obtain a specific attribute.
Now your question is how to obtain the associated primary key from the name of the event, I will show you below some solutions with consequences to how you declared the model.
A. Using scenes.objects.get():
If you use this method you must take into consideration two things:
That the search exists, it not exit, it raises Model.DoesNotExist exception.
That the search return only 1 object,it finds more than one object, it raises a Model.MultipleObjectsReturned exception:
please see the Queryset get() method
so if we ignore the second thing, the block of code is like this
# we have 1 object with the name `world`
try:
scene = scenes.objects.get(name="world")
scene_pk = scene.pk
except scenes.DoesNotExist:
print("The Scene does not Exists")
but the second you should use Queryset filter() method with other methods like first() or last()
So I recommend you re-make the Model like this:
class scenes(models.Model):
name = models.CharField('Event name',unique=True, max_length=120)
record_date = models.DateTimeField('Event date')
or using a SlugField like pk or unique field if you don't wanna change the id as primary key
class scenes(models.Model):
name = models.CharField('Event name', max_length=120)
slug = models.SlugField(unique=True)
record_date = models.DateTimeField('Event date')
the slugfield is ideal if you wish to use the name of the event in the URL.

Related

Using Django Admin panel object creation functionality with Multiple-to-Multiple models

I am trying to use django admin board functionality to be able to insert data within admin panel into tables with one-to-multiple and multiple-to-multiple relations. I used __str__ functionality (Python 3.6) in order to create understandable strings in admin panel instead of "Testcase object(1)" like description to be able to select proper testcase id and testrun id for executedtests model. But it looks like admin panel during executedtest object creation instead of testcase id value and testrun id value tries to set foreign int keys to a corresponding text value.
I miserably fail with error:
tc = Testcases.objects.get(pk=self.testcase_id)
...
TypeError: int() argument must be a string, a bytes-like object or a number, not 'Testcases'
Models:
class Testcases(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return f"Testcase: {self.name}"
class Testruns(models.Model):
starttime = models.TimeField()
endtime = models.TimeField()
def __str__(self):
return f"Testrun: {self.starttime} - {self.endtime}"
class Executedtests(models.Model):
testcase_id = models.ForeignKey("testcases", models.CASCADE)
testrun_id = models.ForeignKey("testruns", models.CASCADE)
teststart = models.TimeField(blank=True, null=True) # This field type is a guess.
testend = models.TimeField(blank=True, null=True) # This field type is a guess.
def __str__(self):
tc = Testcases.objects.get(pk=self.testcase_id)
tr = Testruns.objects.get(pk=self.testrun_id)
return f"{tc}(tr), Start: {self.teststart}, End: {self.testend}"
Inside app admin.py I just register all those models.
The select tag of the admin form looks ok though:
<select id="id_testcase_id" name="testcase_id">
<option value="1">Testcase: Test1</option>
...
</select>
If you define a fieldname that is a ForeignKey, Django automatically adds an extra field fieldname_id that contains the primary key of that object. So here Django has constructed a testcase_id_id, which contains the primary key of the Testcases instance it refers to, but this makes it semantically rather "ugly".
It is therefore more consistent to name the relation after the item it refers to, and let Django generate the fieldname_id, such that you can still extract (and query with) that value.
A minimal fix is thus:
class Executedtests(models.Model):
# rename the relations (drop the _id suffix)
testcase = models.ForeignKey("testcases", models.CASCADE)
testrun = models.ForeignKey("testruns", models.CASCADE)
teststart = models.TimeField(blank=True, null=True) # This field type is a guess.
testend = models.TimeField(blank=True, null=True) # This field type is a guess.
def __str__(self):
tc = Testcases.objects.get(pk=self.testcase_id)
tr = Testruns.objects.get(pk=self.testrun_id)
return f"{tc}(tr), Start: {self.teststart}, End: {self.testend}"
Your view will then work, since testcase_id will contain no longer a Testcases instance (the foreign key takes the type of the model you refer to), but the primary key.
By calling someexecutedtests.testcase you thus do not obtain the primary key of that testcase, but the Testcases instance itself (in case it was not fetched yet, Django will usually perform another query).

Setting default value for Foreign Key attribute

What is the best way to set a default value for a foreign key field in a model? Suppose I have two models, Student and Exam with student having exam_taken as foreign key. How would I ideally set a default value for it? Here's a log of my effort
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=1)
Works, but have a hunch there's a better way.
def get_exam():
return Exam.objects.get(id=1)
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=get_exam)
But this fails with tables does not exist error while syncing.
Any help would be appreciated.
I would modify #vault's answer above slightly (this may be a new feature). It is definitely desirable to refer to the field by a natural name. However instead of overriding the Manager I would simply use the to_field param of ForeignKey:
class Country(models.Model):
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, to_field='sigla', default='IT')
As already implied in #gareth's answer, hard-coding a default id value might not always be the best idea:
If the id value does not exist in the database, you're in trouble. Even if that specific id value does exist, the corresponding object may change. In any case, when using a hard-coded id value, you'd have to resort to things like data-migrations or manual editing of existing database content.
To prevent that, you could use get_or_create() in combination with a unique field (other than id).
Here's one way to do it:
from django.db import models
class Exam(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=255)
#classmethod
def get_default_pk(cls):
exam, created = cls.objects.get_or_create(
title='default exam',
defaults=dict(description='this is not an exam'),
)
return exam.pk
class Student(models.Model):
exam_taken = models.ForeignKey(
to=Exam, on_delete=models.CASCADE, default=Exam.get_default_pk
)
Here an Exam.title field is used to get a unique object, and an Exam.description field illustrates how we can use the defaults argument (for get_or_create) to fully specify the default Exam object.
Note that we return a pk, as suggested by the docs:
For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.
Also note that default callables are evaluated in Model.__init__() (source). So, if your default value depends on another field of the same model, or on the request context, or on the state of the client-side form, you should probably look elsewhere.
I use natural keys to adopt a more natural approach:
<app>/models.py
from django.db import models
class CountryManager(models.Manager):
"""Enable fixtures using self.sigla instead of `id`"""
def get_by_natural_key(self, sigla):
return self.get(sigla=sigla)
class Country(models.Model):
objects = CountryManager()
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, default='IT')
In my case, I wanted to set the default to any existing instance of the related model. Because it's possible that the Exam with id 1 has been deleted, I've done the following:
class Student(models.Model):
exam_taken = models.ForeignKey("Exam", blank=True)
def save(self, *args, **kwargs):
try:
self.exam_taken
except:
self.exam_taken = Exam.objects.first()
super().save(*args, **kwargs)
If exam_taken doesn't exist, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist will be raised when a attempting to access it.
The issue with most of these approaches are that they use HARD CODED values or lambda methods inside the Model which are not supported anymore since Django Version 1.7.
In my opinion, the best approach here is to use a sentinel method which can also be used for the on_delete argument.
So, in your case, I would do
# Create or retrieve a placeholder
def get_sentinel_exam():
return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]
# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
return get_sentinel_exam().id
class Exam(models.Model):
....
# Making some madeup values
name=models.CharField(max_length=200) # "English", "Chemistry",...
year=models.CharField(max_length=200) # "2012", "2022",...
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam",
on_delete=models.SET(get_sentinel_exam),
default=get_sentinel_exam_id
)
Now, when you just added the exam_taken field uses a guaranteed existing value while also, when deleting the exam, the Student themself are not deleted and have a foreign key to a deleted value.
You could use this pattern:
class Other(models.Model):
DEFAULT_PK=1
name=models.CharField(max_length=1024)
class FooModel(models.Model):
other=models.ForeignKey(Other, default=Other.DEFAULT_PK)
Of course you need to be sure that there is a row in the table of Other. You should use a datamigration to be sure it exists.
I'm looking for the solution in Django Admin, then I found this:
class YourAdmin(admin.ModelAdmin)
def get_changeform_initial_data(self, request):
return {'owner': request.user}
this also allows me to use the current user.
see django docs
the best way I know is to use lambdas
class TblSearchCase(models.Model):
weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))
so you can specify the default row..
default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')

Django: making relationships in memory without saving to DB

I have some models with relationships like this:
class Item(model.Model):
name = models.CharField()
class Group(models.Model):
item = models.ManyToManyField(Item)
class Serie(models.Model):
name = models.CharField()
chart = models.ForeignKey(Chart)
group = models.ForeignKey(Group)
class Chart(models.Model):
name = models.CharField()
I need to create a Chart object on the fly, without saving to the DB. But I can't do it because Django tries to use the objects primary keys when assigning the relationships.
I just want Group.add(Item()) to work without having to save the objects to the DB.
Is there any simple way around this?
Reviving here for the sake of future readers:
I've gotten around this use case by defining a private attribute that represents the relationship inside the classes and a property to inspect wether the object can be retrieved from the DB or resides in memory.
Here is a simple example:
class Parent(models.Model):
_children = []
name = models.CharField(max_length=100)
#property
def children(self):
if _children:
return self._children
else:
return self.children_set.all()
def set_virtual_children(self, value): # could use a setter for children
self._children = value # Expose _children to modification
def some_on_the_fly_operation(self):
print(','.join([c.name for c in self.children]))
class Children(models.Model):
parent = models.ForeignKey(Parent)
name = models.CharField(max_length=100)
This way, I can set the "virtual children" and use all the defined methods "on the fly"
EDIT: It seems that approach described here isn't enough for django to allow adding to the ManyToMany relationship.
Have you tried to add primary_key=True and unique=True to the name attribute of the Item model. Then doing Group.add(Item("item_name_here")) should work if you have the possibility to create the name on the fly.
I didn't test it, but I think your way failed because add() wants to use the primary-key which by default is the autoincrementing id that is assigned when it is saved to the database.

django views - accessing a m2m field in a generic view

I've stumbled upon this issue and my noob brain got fried trying to resolve it. I feel like there's some basic concepts here that I'm missing.
So I have this "Films" model with category choice field and a m2m relationship to a "Directors" model, and I'm trying to write 2 different views, one that returns a list of films filtered by category and one that returns a list of films filtered by director.
The first one is easy, but I just don't know how to get the director model's name field to create the second filter.
So I have this models (i've taken the irrelevant stuff out including the category thing i mentioned above)
class Director(models.Model):
name = models.CharField(max_length=50)
web = models.URLField(blank=True, help_text= "opcional")
class Film(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length= 15)
director = models.ManyToManyField(Director, blank=True, help_text= "opcional")
this url
(r'^peliculas/director/(?P<director>\w+)/$', 'filtered_by_director'),
and this view
def filtered_by_director(request,director):
return list_detail.object_list(
request,
queryset = Film.objects.filter(director.name=director),
template_name ='sections/film_list.html',
template_object_name = 'film',
paginate_by = 3
)
The same template is supposed to be used by both views to render the relevant list of objects
The view doesn't like the filter i'm using at the queryset for the m2m field, but I have no clue how to do it really, I've tried whatever I could think of and it gives me a "keyword can't be an expression" error
Any help to this lowly noob will be appreciated.
Line queryset = Film.objects.filter(director.name=director),
needs to read: queryset = Film.objects.filter(director__name=director),
Field lookups are done by __ double underscore syntax:
http://docs.djangoproject.com/en/dev/topics/db/queries/#field-lookups
In your filter, try specifying the director name like (documentation):
filter(director__name=director)

Django unique_together doesn't work with ForeignKey=None

I saw some ppl had this problem before me, but on older versions of Django, and I'm running on 1.2.1.
I have a model that looks like:
class Category(models.Model):
objects = CategoryManager()
name = models.CharField(max_length=30, blank=False, null=False)
parent = models.ForeignKey('self', null=True, blank=True, help_text=_('The direct parent category.'))
class Meta:
unique_together = ('name', 'parent')
Whenever i try to save in the admin a category with a parent set to None, it still works when there's another category with the SAME name and parent set to None.
Ideas on how to solve this gracefully?
The unique together constraint is enforced at the database level, and it appears that your database engine does not apply the constraint for null values.
In Django 1.2, you can define a clean method for your model to provide custom validation. In your case, you need something that checks for other categories with the same name whenever the parent is None.
class Category(models.Model):
...
def clean(self):
"""
Checks that we do not create multiple categories with
no parent and the same name.
"""
from django.core.exceptions import ValidationError
if self.parent is None and Category.objects.filter(name=self.name, parent=None).exists():
raise ValidationError("Another Category with name=%s and no parent already exists" % self.name)
If you are editing categories through the Django admin, the clean method will be called automatically. In your own views, you must call category.fullclean().
I had that problem too and solved it by creating a supermodel with clean method (like Alasdair suggested) and use it as base class for all my models:
class Base_model(models.Model):
class Meta:
abstract=True
def clean(self):
"""
Check for instances with null values in unique_together fields.
"""
from django.core.exceptions import ValidationError
super(Base_model, self).clean()
for field_tuple in self._meta.unique_together[:]:
unique_filter = {}
unique_fields = []
null_found = False
for field_name in field_tuple:
field_value = getattr(self, field_name)
if getattr(self, field_name) is None:
unique_filter['%s__isnull'%field_name] = True
null_found = True
else:
unique_filter['%s'%field_name] = field_value
unique_fields.append(field_name)
if null_found:
unique_queryset = self.__class__.objects.filter(**unique_filter)
if self.pk:
unique_queryset = unique_queryset.exclude(pk=self.pk)
if unique_queryset.exists():
msg = self.unique_error_message(self.__class__, tuple(unique_fields))
raise ValidationError(msg)
Unfortunately, for those of us using PostgreSQL as our backend database engine, there will never have a fix for this issue:
"Currently, only B-tree indexes can be declared unique.
When an index is declared unique, multiple table rows with equal indexed values are not allowed. Null values are not considered equal. A multicolumn unique index will only reject cases where all indexed columns are equal in multiple rows.
PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table. The index covers the columns that make up the primary key or unique constraint (a multicolumn index, if appropriate), and is the mechanism that enforces the constraint."
Source: https://www.postgresql.org/docs/9.0/indexes-unique.html