Django - dynamic model parameter value - python

I have two simple models with ForeignKey relation, Category and Object let say (Object has FK attribute to Category) and in administration I need to assign value of another object attribute value base on the actually selected Category.
Example:
I will create in Django Admin interface Category with attribute cat_name="A" and another Category with cat_name="B".
Now in the Object creation form I can select in the form "A" or "B" Category, and based on that selection I need to store in Object.description attribute something like "Selected category is B"
I've tried several approaches but all ended on the fact that the instance of the Category object has to be somehow passed to the Object creation form.
Thanks

You do this before saving you data by overriding the save function:
class ObjectModel(models.Model):
category = models.ForeignKey(# details goes here)
..... # other fields goes here
def save(self, *args, **kwargs):
if self.category.name == 'A':
self.description = ...
elif self.category.name == 'B':
.... # different behavior etc
super(ObjectModel, self).save(*args, **kwargs)

OK, finaly I have find acceptable solution. I'm creating the related object at the time of Category object is being saved and passing it's attribute to the object. Something like
class Category(models.Model):
name = models.CharField(max_length=10)
...
def save(self, *args, **kwargs):
super(Category, self).save(*args, **kwargs)
Object.objects.create(name=self.name)
Only disadvantage I've noticed that Objects created this automated way has to have empty (if allowed) or machine generated attributed, but that's just a minor defect for me and I can do any updates via common administration form is needed.

Related

Message Object has no attribute 'fields' when updating model field across apps

I have two apps menu and table. In app table, I have this model:
class Table(models.Model):
available = models.BooleanField(verbose_name="Availability", default=True)
def set_availability(self, avail=False):
self.fields['available'] = avail
self.save()
def __str__(self):
return "Table " + str(self.id_num)
In one of the views of app menu, I have the following call:
from table.models import Table
def menu_category_view(request, table_pk):
table = Table.objects.get(pk=table_pk)
if table.available:
table.set_availability(False)
...
return render(request,
...)
When my template calls this view, I receive this error message 'Table' object has no attribute 'fields'. Here, I am trying to update the value of field available of the instance being called (from True to False). And I got this implementation suggested from a book. Is this the right way to update model instance field value? Thanks.
Just set the attribute.
def set_availability(self, avail=False):
self.available = avail
self.save()
Though, it's questionable whether or not set_<field> methods like this are particularly useful. You could work with the object almost as easily:
if table.available:
table.available = False
table.save()

How should i auto fill a field and make it readonly in django?

I`m new to django and i was doing a test for my knowledge.
Found a lot of duplicates in here and web but nothing useful
I'm trying to make a ForeignKey field which gets filled due to the other fields that user fills, and make it unchangeable for the user.
I thought that I should use overriding save() method but couldn't figure that at all.
How should I do that auto-fill and read-only thing?
Your approach is right. Override the save method and if self.pk is not None raise an exception if your field has changed. You can use django model utils to easily track changes in your model: https://django-model-utils.readthedocs.io/en/latest/utilities.html#field-tracker
Principle:
class MyModel(models.Model):
#....
some_field = models.Foreignkey(...)
tracker = FieldTracker()
def save(*args, **kwargs):
if self.pk is None:
# new object is being created
self.some_field = SomeForeignKeyObject
else:
if self.tracker.has_changed("some_field"):
raise Exception("Change is not allowed")
super().save(*args, **kwargs)

how can I fix ManyToManyDescriptor from a lesson class model - view?

I am trying to build a single course platorm where I will only hold lessons units materials where only people with membership will be able to see it , however when I try to do retrieve Lesson.course_allowed_mem_types.all() I got the following error 'ManyToManyDescriptor' object has no attribute 'all' , how can I fix this simple error?
class Lesson(models.Model):
content_title = models.CharField(max_length=120)
content_text = models.CharField(max_length=200)
thumbnail = models.ImageField(upload_to='static/xxx/xxx/xxx/xxx')
link = models.CharField(max_length=200, null=True)
allowed_memberships = models.ManyToManyField(Membership)
​
def __str__(self):
return self.content_title
views
def get_context_data(self, **kwargs):
context = super(bootCamp, self).get_context_data(**kwargs)
context['lessons'] = Lesson.objects.all()
user_membership = UserMembership.objects.filter(user=self.request.user).first()
user_membership_type = user_membership.membership.membership_type
course_allowed_mem_types = Lesson.allowed_memberships.all()
context['course_allowed_mem_types'] = course_allowed_mem_types
return context
You can query many-to-many related field only for model instance, not model class. It's not really clear what exactly is "all concrete allowed membership objects for a Lesson class" (Lesson.allowed_memberships.all()).
Is it "all membership objects related to any of existing lesson objects" or is it "all membership objects that can be related to a lesson object"?
Those are different queries, and Lesson.allowed_memberships.all() does not imply either, it's incorrect usage.
If you want the former, something like this could work
Membership.objects.filter(lesson__in=Lesson.objects.all())
(You already have this as context['lessons'] so use that instead, just showing the idea)
I think,
One lesson may have many memberships. so you are selecting all lessons with all memberships Lesson.allowed_memberships.all() .
Try selecting a single lesson then retrieve associated members
lesson = Lessons.objects.filter(pk=1)
course_allowed_mem_types = lesson.allowed_memberships.all()
If you want to create custom list like type, it is always a good idea to inherit from collections.abc.Iterable. It provides common operations required to work on such container types.
You can't just call .all() on any object/type, that type definition actually has to have all() method defined in class or parent class.
e.g.
class ListLike:
def __init__(self):
...
def all(self):
return some_iterator

Django Rest Framework - Read nested data, write integer

So far I'm extremely happy with Django Rest Framework, which is why I alsmost can't believe there's such a large omission in the codebase. Hopefully someone knows of a way how to support this:
class PinSerializer(serializers.ModelSerializer):
item = ItemSerializer(read_only=True, source='item')
item = serializers.IntegerSerializer(write_only=True)
class Meta:
model = Pin
with the goal
The goal here is to read:
{pin: item: {name: 'a', url: 'b'}}
but to write using an id
{pin: item: 10}
An alternative would be to use two serializers, but that looks like a really ugly solution:
django rest framework model serializers - read nested, write flat
Django lets you access the Item on your Pin with the item attribute, but actually stores the relationship as item_id. You can use this strategy in your serializer to get around the fact that a Python object cannot have two attributes with the same name (a problem you would encounter in your code).
The best way to do this is to use a PrimaryKeyRelatedField with a source argument. This will ensure proper validation gets done, converting "item_id": <id> to "item": <instance> during field validation (immediately before the serializer's validate call). This allows you to manipulate the full object during validate, create, and update methods. Your final code would be:
class PinSerializer(serializers.ModelSerializer):
item = ItemSerializer(read_only=True)
item_id = serializers.PrimaryKeyRelatedField(write_only=True,
source='item',
queryset=Item.objects.all())
class Meta:
model = Pin
fields = ('id', 'item', 'item_id',)
Note 1: I also removed source='item' on the read-field as that was redundant.
Note 2: I actually find it rather unintuitive that Django Rest is set up such that a Pin serializer without an Item serializer specified returns the item_id as "item": <id> and not "item_id": <id>, but that is beside the point.
This method can even be used with forward and reverse "Many" relationships. For example, you can use an array of pin_ids to set all the Pins on an Item with the following code:
class ItemSerializer(serializers.ModelSerializer):
pins = PinSerializer(many=True, read_only=True)
pin_ids = serializers.PrimaryKeyRelatedField(many=True,
write_only=True,
source='pins',
queryset=Pin.objects.all())
class Meta:
model = Item
fields = ('id', 'pins', 'pin_ids',)
Another strategy that I previously recommended is to use an IntegerField to directly set the item_id. Assuming you are using a OneToOneField or ForeignKey to relate your Pin to your Item, you can set item_id to an integer without using the item field at all. This weakens the validation and can result in DB-level errors from constraints being violated. If you want to skip the validation DB call, have a specific need for the ID instead of the object in your validate/create/update code, or need simultaneously writable fields with the same source, this may be better, but I wouldn't recommend anymore. The full line would be:
item_id = serializers.IntegerField(write_only=True)
If you are using DRF 3.0 you can implement the new to_internal_value method to override the item field to change it to a PrimaryKeyRelatedField to allow the flat writes. The to_internal_value takes unvalidated incoming data as input and should return the validated data that will be made available as serializer.validated_data. See the docs: http://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data
So in your case it would be:
class ItemSerializer(ModelSerializer):
class Meta:
model = Item
class PinSerializer(ModelSerializer):
item = ItemSerializer()
# override the nested item field to PrimareKeyRelatedField on writes
def to_internal_value(self, data):
self.fields['item'] = serializers.PrimaryKeyRelatedField(queryset=Item.objects.all())
return super(PinSerializer, self).to_internal_value(data)
class Meta:
model = Pin
Two things to note: The browsable web api will still think that writes will be nested. I'm not sure how to fix that but I only using the web interface for debug so not a big deal. Also, after you write the item returned will have flat item instead of the nested one. To fix that you can add this code to force the reads to use the Item serializer always.
def to_representation(self, obj):
self.fields['item'] = ItemSerializer()
return super(PinSerializer, self).to_representation(obj)
I got the idea from this from Anton Dmitrievsky's answer here: DRF: Simple foreign key assignment with nested serializers?
You can create a Customized Serializer Field (http://www.django-rest-framework.org/api-guide/fields)
The example took from the link:
class ColourField(serializers.WritableField):
"""
Color objects are serialized into "rgb(#, #, #)" notation.
"""
def to_native(self, obj):
return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
def from_native(self, data):
data = data.strip('rgb(').rstrip(')')
red, green, blue = [int(col) for col in data.split(',')]
return Color(red, green, blue)
Then use this field in your serializer class.
I create a Field type that tries to solve the problem of the Data Save requests with its ForeignKey in Integer, and the requests to read data with nested data
This is the class:
class NestedRelatedField(serializers.PrimaryKeyRelatedField):
"""
Model identical to PrimaryKeyRelatedField but its
representation will be nested and its input will
be a primary key.
"""
def __init__(self, **kwargs):
self.pk_field = kwargs.pop('pk_field', None)
self.model = kwargs.pop('model', None)
self.serializer_class = kwargs.pop('serializer_class', None)
super().__init__(**kwargs)
def to_representation(self, data):
pk = super(NestedRelatedField, self).to_representation(data)
try:
return self.serializer_class(self.model.objects.get(pk=pk)).data
except self.model.DoesNotExist:
return None
def to_internal_value(self, data):
return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
And so it would be used:
class PostModelSerializer(serializers.ModelSerializer):
message = NestedRelatedField(
queryset=MessagePrefix.objects.all(),
model=MessagePrefix,
serializer_class=MessagePrefixModelSerializer
)
I hope this helps you.

Django : Foreign key set not found after saving object

I've got a question model and mcq choices model which have foreign key to question.
class Question(models.Model):
statement = models.TextField(max_length=1024)
def save(self, *args, **kwargs):
super(Question,self).save(*args,**kwargs)
#ques = Question.objects.get(id = self.id)
f = open('/tmp/prj/log.txt', 'w')
choiceobjs = self.choice_set.all()
if choiceobjs:
f.write("choices found")
else:
f.write("choices not found.. zilch")
f.close()
class Choice(models.Model):
value = models.TextField(max_length=1024)
question = models.ForeignKey(Question)
Now I've overridden the save method of question. Even after the question has been saved, I cannot find choice_set in save method! I always get "choices not found.. zilch" in my logfile.
UPDATE: I'm creating my Question in Admin interface, and 'Choice' objects are being created 'inline'.
So the modified question is - In what sequence do the 'inline' fields/models and the main model get created? How can I delay my check for foreignkey set in save method, such that 'foreignkey_set' becomes visible?
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo)
When using inlines of Bar in the Foo admin, Django has to save the Foo object first, because the Bar objects need the primary key to reference it in a ForeignKey:
self.save_model(request, new_object, form, change=False)
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=False)
http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L870
That means when Foo's save method is called, the inline Bar objects haven't been saved yet, and therefore can't be queried. So you need to work around this, if you need to access these objects when a Foo instance was saved in the admin (using Bar inlines).
One possible solution would be to attach to a post_save signal of Bar, see which Foo object it is referencing, and executing the relevant code. But this would trigger on every change, even if no Foo object was created.

Categories