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

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).

Related

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

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.

Create a lookup in django

I Have an accounts table with account number and account name in a model called accounts
how do I create a lookup such that whenever I enter an account number in django template, account name get populated automatically
my models at attempt are
class Account(models.Model):
account_number = models.IntegerField()
account_name = models.CharField(max_length=50)
bank_name = models.ForeignKey(Bank, on_delete=models.CASCADE)
status = models.CharField(max_length=50)
def __str__(self):
return account_name
def get_absolute_url(self):
return reverse_lazy('accounts')
class AccountLookup(models.Model):
account_number = models.ForeignKey(Account, on_delete=models.CASCADE):
account_name = models. ???????????
If you want something you can use in a template that will render account name starting from an AccountLookup object, then its
{{accountlookup_instance.account_number.account_name}}
Note that this will hit the DB again, unless you used select_related() on the queryset which obtained the AccountLookup object(s) in the first place.
Python code can likewise use this dotted path, with the same caveat.
You might regard it as a simplification to be able to refer to what looks like a field (but isn't). In which case you define it as a property
class AccountLookup(models.Model):
account_number = models.ForeignKey(Account, on_delete=models.CASCADE)
...
#property
def account_name(self):
return self.account_number.account_name
(It gets more useful if you need to apply some standard formatting to the account name to convert it into a more human-readable form in this context).
By the way, calling it account_number is confusing. Better to just name a ForeignKey field for what it is. account in this case: the account object linked by a ForeignKey to this object. Yes, it's represented internally by an account_id which is commonly an integer (the auto-generated primary key), but that's a low level detail Django programmers are not often concerned with.

Changing name of Foreign Key items at admin page in Django

I have created two Django models, were a field from one model is a Foreign key to another (as per below).
class Units(models.Model):
name = models.CharField(max_length=10, primary_key=True)
symbol = models.CharField(max_length=5)
class Targets(models.Model):
name = models.CharField(max_length=100)
unit = models.ForeignKey(MicroNutrientUnits)
...
These models are then registered to the admin site via admin.site.register. When adding an item to table Target unit correctly reflects items from table Units, however are represented in a dropdown with the name Units objects for each entry.
How can I set the name in the admin dropdown to Units.name in the admin config page?
I had the same issue when I started playing with the admin screens. I don't know if there is a better way to do this but what I did was modify the models.py file to tell my classes what to return as a default string. In your case what I would do is add the following to your Units class definition.
class Units(models.Model):
name = models.CharField(max_length=10, primary_key=True)
symbol = models.CharField(max_length=5)
def __str__(self):
return self.name
This way, when you use the foreign key link to the table, django will display the name of each unit in the dropdown list instead of just the word 'Units' over and over.

m2m for existing table (through/unique_together)

I have found in internet different examples on how to handle m2m relations with existing DB models, such as ex1 or here ex2, however I'm still not able to solve the error I get.
My models are depicted below. Basically, all the tables where created manually.
I got the following error message:
OperationalError: (1054, "Unknown column 'supervisor_project.id' in 'field list'").
I'm still a bit confused on when to use unique_together with through. Do you see any errors in the model below? The table supervisor_project has no id field and its PK is composed actually of two FK's, i.e. surrogate PK.
class Supervisor(models.Model):
name = models.CharField(max_length=45, blank=True, null=True, help_text="Name, e.g. John Smith")
class Meta:
managed = False
db_table = 'supervisor'
def __unicode__(self):
return self.name
class Project(models.Model):
title = models.CharField(max_length=45, blank=True, null=True)
supervisors = models.ManyToManyField(Supervisor, through='SupervisorProject', through_fields=('project', 'supervisor'))
class SupervisorProject(models.Model):
supervisor = models.ForeignKey('Supervisor', on_delete=models.CASCADE)
project = models.ForeignKey('Project', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'supervisor_project'
unique_together = (('supervisor', 'project'),)
Django requires each model to have exactly one primary key field. It doesn't support multiple-column primary keys yet.
Since you haven't explicitly defined a primary key on the SupervisorProject model, Django assumes that there is an automatic primary key field id. When it includes the id field in a query, you get the error because it doesn't exist.
If possible, I would add an auto-incrementing id column to each intermediate table. There isn't a way to get Django to add the column to the tables automatically. You have set managed=False, so Django expects you to manage the database table.

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')