Django Models: Default for Choice Foreign Key - python

I have a Type model class as follows:
class Type(models.Model):
ENVIRONMENT = 'A'
HUMANENV = 'B'
HUMAN = 'C'
ANIMAL = 'D'
UNKNOWN = 'H'
TYPE_CHOICES = [
(ENVIRONMENT, 'Environment'),
(HUMANENV, "Human-related Environment"),
(HUMAN, 'Human'),
(ANIMAL, 'Animal'),
(UNKNOWN, 'Unknown'),
]
code = models.CharField(max_length=1, choices=TYPE_CHOICES, unique=True)
class Meta:
ordering = ['code']
def __str__(self):
return self.get_code_display()
And another Sample model where one of the fields is a foreign key to the Type model as follows:
class Sample(models.Model):
sample_id = models.CharField(max_length=20, unique=True)
type = models.ForeignKey("Type", on_delete=models.CASCADE, blank=True, default=get_default_type())
class Meta:
ordering = ["sample_id"]
def __str__(self):
return self.sample_id
where get_default_type is a function that returns the pk for the default Type model instance:
def get_default_type():
return Type.objects.get(code="H").id
The problem is when I run Sample.objects.create(sample_id="some_id"), it is giving me the error
IntegrityError: null value in column "type_id" violates not-null constraint
DETAIL: Failing row contains (28113, some_id, null).
As you can see in the second line of the error message, the type_id is null instead of the pk as returned by the get_default_type function.
I have tried setting null=True for the foreign key and when I do that I am able to create the Sample model instance, but with a None type instead of the Unknown type as I wanted. How can I fix this?

Two solutions:
Override the manager
From this response you could use get_by_natural_key in your manager.
In managers.py
from django.db import models
class TypeManager(models.Manager):
"""Enable fixtures using self.sigla instead of `id`"""
def get_by_natural_key(self, code):
return self.get(code=code)
class Type(models.Model):
#.... Declare your model here
objects = Type()
or...
Change your pk!
class Type(models.Model):
#.... Declare your model here
code = models.CharField(max_length=1, choices=TYPE_CHOICES, unique=True, primary_key=True)
Either way, in your related model declaration:
class Sample(models.Model):
type = models.ForeignKey("Type", on_delete=models.CASCADE, blank=True, default='H')
class Meta:
ordering = ["sample_id"]
def __str__(self):
return self.sample_id
As a side note: Please take care about type.
It is a protected keyword and should not be used.

Related

How to make more than one fields primary key and remove auto generated id in django models

Suppose in a relational database schema we have a student, a subject and a teacher which connect to each other with a relation teaches. Also, the relation has an attribute time that stores the time of the lesson. This is the most complete yet simplified example I can think to describe my case. Now, the most pythonic and django-wise way I can think of trying to reach a correct solution is, after creating a model class for student, subject and teacher, to create a new class Teaches, which has the foreign keys for the three other classes; also it has the property date field for time. This class would look something like this:
class Teaches(models.Model):
teachers = models.ForeignKey(Teacher, on_delete_models.CASCADE)
subjects = models.ForeignKey(Subject, on_delete_models.CASCADE)
students = models.ForeignKey(Student, on_delete_models.CASCADE)
time = models.DateField
class Meta:
constraints = [
fields=['teachers', 'subjects', 'students']
name='teacher_subject_student_triplet'
]
I added the Meta class because this is what this answer recommends as the correct approach.
The problem is that that in the migrations file I can still see the id field. The only way I've seen there is to remove it is to set another field as Primary Key, but in my case I cannot do that, having more than one keys. Any suggestions?
=========== model.py =============
from django.db import models
class TeacherModel(models.Model):
teacher_code = models.CharField(max_length=255)
def __str__(self):
return self.teacher_code
class SubjectModel(models.Model):
subject_code = models.CharField(max_length=255)
def __str__(self):
return self.subject_code
class StudentModel(models.Model):
student_code = models.CharField(max_length=255)
def __str__(self):
return self.student_code
class Teaches(models.Model):
custom_primary_key = models.SlugField(primary_key=True,blank=True)
teacher = models.ForeignKey(TeacherModel, on_delete=models.CASCADE)
subject = models.ForeignKey(SubjectModel, on_delete=models.CASCADE)
student = models.ForeignKey(StudentModel, on_delete=models.CASCADE)
time = models.DateField
#property
def make_key(self):
new_key = str(self.teacher.teacher_code + self.subject.subject_code + self.student.student_code)
return new_key
def save(self, *args, **kwargs):
self.custom_primary_key = self.make_key
super(Teaches, self).save(*args, **kwargs)
========= Output ==============
You can remove autogenerated id by adding primary_key=True, see below code:
class Person(models.Model):
username = CharField(primary_key=True, max_length=100)
first_name = CharField(null=True, blank=True, max_length=100)
setting a field to primary_key=True automatically makes it unique and not null.
In settings.py:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Controls the automatic generation of primary keys of each model if defined in settings.
Read this article:
Set AutoField or BigAutoField on a per model basis

How can I display all fields from a Many-to-Many Model in Django Admin

I have the following Models:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
class ModelB(models.Model):
some_field_B = models.CharField()
many_to_many_relation = models.ManyToManyField(ModelA)
In admin.py I am using filter_horizontal to edit the ManyToManyField:
class ModelB(admin.ModelAdmin):
model = ModelB
filter_horizontal = ('many_to_many_relation',)
but it shows only some_field_A and I want it to show both fields from ModelA, because the entries in ModelA are unique depending on both fields and as you can see from the picture there are multiple entries with the same value (i.e. some_field_A = EUV) but they have different values for some_other_field_A:
It displays the result of the __str__(…) method you defined in your ModelA, so if you return the value of some_field in the __str__(…) method, then it will return only the data of some_field.
You thus can alter this method and return both fields:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
def __str__(self):
return f'{self.some_field_A} {self.some_other_field_A}'
I'm not sure if this exactly the solution you are looking for but you could override the __str__ method of ModelA to return the information in a single line.
So for example:
class ModelA(models.Model):
first_field = models.CharField(max_length=16)
second_field = models.CharField(max_length=16)
def __str__(self):
return f"{self.first_field} ({self.second_field'})"
Your admin view should then show each object as "foo (bar)"

access attribute via serializer django

I got following models:
class OrderItem(models.Model):
ordered_amount = models.IntegerField(validators=[MinValueValidator(0)])
amount = models.IntegerField(default=0)
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="order_items"
)
class Order(models.Model):
reference = models.CharField(max_length=50)
purchase_order = models.CharField(max_length=15, blank=True, null=True)
I'm now writing a serializer for listing orders. In this OrderSerializer I need to access amount and ordered_amount in the OrderItem class. How do I do this?
This is What I have now:
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.IntegerField()
ordered_amount = serializers.IntegerField()
class Meta:
model = Order
fields = [
"purchase_order",
"reference",
"amount",
"ordered_amount",
]
# noinspection PyMethodMayBeStatic
def validate_amount(self, order):
if order.order_items.amount:
return order.order_items.amount
return
# noinspection PyMethodMayBeStatic
def validate_ordered_amount(self, order):
if order.order_items.ordered_amount:
return order.order_items.ordered_amount
return
This gives me following error:
AttributeError: Got AttributeError when attempting to get a value for field amount on serializer AdminOrderItemListSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Order instance.
Original exception text was: 'Order' object has no attribute 'amount'.
There are many ways to that, one of them is SerializerMethodField:
from django.db.models import Sum
class AdminOrderListSerializer(serializers.ModelSerializer):
amount = serializers.SerializerMethodField()
ordered_amount = serializers.SerializerMethodField()
def get_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('amount'))['sum']
def get_ordered_amount(self,obj):
return obj.order_items.aggregate(sum=Sum('order_amount'))['sum']
Optimized solution
Another way of achieving this is to annotate the data to queryset, and access them in serializer. For that, you need to change in view:
class SomeView(ListAPIView):
queryset = Order.objects.annotate(amount=Sum('order_items__amount'),order_amount=Sum('order_items__order_amount'))
This is a optimized solution because it reduces database hits(it only hits once).

Django Models - How do you add subtype choice when user selects choice?

I'm working on a project where they have various job types that I've tackled with CHOICES, however, I want to add conditionals for WHEN job type 1 is chosen, SUBTYPES x-y become choices. I am having trouble with the syntax of how you would do that. I've included my pseudocode below... I appreciate any help!
from django.db import models
class User(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='jobs')
JOB_CHOICES = (
('carpentry', 'Carpentry'),
('cleaning', 'Cleaning'),
('electrician', 'Electrician'),
('handyman', 'Handyman'),
('hvac', 'HVAC'),
('painting', 'Painting'),
('pest', 'Pest'),
('plumbing', 'Plumbing'),
('roofing', 'Roofing'),
('property', 'Property'),
)
jobType = models.CharField(max_length=30, choices=JOB_CHOICES, default='handyman')
# If JobType = Carpentry:
# CARPENTRY_CHOICES = (
# ('trim', 'trim')
# ('sheetrock', 'Sheetrock')
# ('windows', 'Windows')
# ('doors', 'Doors')
# ('cabinets and shelving', 'Cabinets and Shelving')
# ('other', 'Other')
# )
# jobType = models.CharField(max_length=30, choices=CARPENTRY_CHOICES, default='other')
def __str__(self):
return self.name
Django Models
Django Serializer
/api editor
I would probably go with a job_type model, which has a name and a 'subtype' field.
class JobType(models.Model):
SubTypeChoices = (...)
name = models.CharField()
subtype = models.CharField(choices=SubTypeChoices, ...)
class Job(models.Model):
....
job_type = models.ForeignKey(JobType, ...)
....
This way you can associate your 'subtypes' with one job_type. And if for some reason you can have several job_types for a Job, use a ManyToMany field.

How to store functions in django models

edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')

Categories