How to assign items inside a Model object with Django? - python

Is it possible to override values inside a Model?
I am getting 'MyModel' object does not support item assignment.
my_model = MyModel.objects.get(id=1)
print my_model.title
if my_model.is_changed:
my_model['title'] = 'something' # 'MyModel' object does not support item assignment
params = {
'my_model': my_model,
...
}
return render(request, 'template.html', params)

Models are objects, not dictionaries. Set attributes on them directly:
if my_model.is_changed:
my_model.title = 'something'
Or, if the attribute name is dynamic, use setattr:
attr_name = 'title' # in practice this would be more complex
if my_model.is_changed:
setattr(my_model, attr_name, 'something')
This changes the in-memory copy of the model, but makes no database changes - for that your attribute would have to be a field and you'd have the call the save method on my_model. You don't need to do that if you just want to change what the template receives in its context, but just for completeness's sake:
if my_model.is_changed:
my_model.title = 'something'
my_model.save()
Dictionaries are mutable, if you actually have a dictionary:
mydict = {'title': 'foo'}
# legal
mydict['title'] = 'something'
But not everything is a dictionary.

Yes, you can change values, but this is not how its done. Django Models are Python classes that have models to represent fields. For example a CharField is for holding a string in a database. Let me demonstrate (code from django docs):
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
As you can see above the Python class is a custom Django model. It is linked to a databse, and when you run manage.py syncdb, it will interact with your database to create the tables and columns that you need it to.
Now, in your case:
if my_model.is_changed:
my_model.title = "Something"
my_model.save()

my_model is an object. So, try this:
if my_model.is_changed:
my_model.title = 'something'
my_model.save()

I was using inlineformset_factory, what I had to do was:
Instead of using my_model.title = 'something',
I had to use my_model.instance.title = 'something'
views.py
...
if request.method == "POST":
formset = modelName2(request.POST, instance=modelName1)
if formset.is_valid():
if changeInstance == True:
models = formset
# change the title if changeInstance is True
index = 0
model = models[index]
model.instance.title = "something else"
model.save()
...

Related

Django: How to use an annotation from a parent queryset?

I have an Item class which can be annotated using a custom queryset add_is_favorite_for method:
class ItemQuerySet(QuerySet):
def add_is_favorite_for(self, user):
"""add a boolean to know if the item is favorited by the given user"""
condition = Q(id__in=Item.objects.filter(favoriters=user).values("id"))
return self.annotate(is_favorite=Condition(condition)) # True or False
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
It works as expected. For example:
>>> user = User.objects.get(id=1)
>>> Item.objects.add_is_favorite_for(user) # each item has now a `is_favorite` field
Then, I added a Factory model and link Item model to it using a 1->N relationship:
class Factory(Model):
pass # ...
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
advised_in = models.ForeignKey(
Factory,
on_delete=models.CASCADE,
related_name="advised_items",
)
Now, I'd like to be able to return a Factory QuerySet, whose advised_items fields will all contain the is_favorite annotation too.
I don't know how to do this, I saw no example of such a thing in the doc, maybe I missed it.
You can work with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
Factory.objects.prefetch_related(
Prefetch('advised_items', queryset=Item.objects.add_is_favorite_for(some_user))
)

How to get the value of a Django Model Field object

I got a model field object using field_object = MyModel._meta.get_field(field_name). How can I get the value (content) of the field object?
Use value_from_object:
field_name = 'name'
obj = MyModel.objects.first()
field_object = MyModel._meta.get_field(field_name)
field_value = field_object.value_from_object(obj)
Which is the same as getattr:
field_name = 'name'
obj = MyModel.objects.first()
field_object = MyModel._meta.get_field(field_name)
field_value = getattr(obj, field_object.attname)
Or if you know the field name and just want to get value using field name, you do not need to retrieve field object firstly:
field_name = 'name'
obj = MyModel.objects.first()
field_value = getattr(obj, field_name)
Assuming you have a model as,
class SampleModel(models.Model):
name = models.CharField(max_length=120)
Then you will get the value of name field of model instance by,
sample_instance = SampleModel.objects.get(id=1)
value_of_name = sample_instance.name
If you want to access it somewhere outside the model You can get it after making an object the Model. Using like this
OUSIDE THE MODEL CLAA:
myModal = MyModel.objects.all()
print(myModel.field_object)
USING INSIDE MODEL CLASS
If you're using it inside class you can simply get it like this
print(self.field_object)
Here is another solution to return the nth field of a model where all you know is the Model's name. In the below solution the [1] field is the field after pk/id.
model_obj = Model.objects.get(pk=pk)
field_name = model_obj._meta.fields[1].name
object_field_value = getattr(model_obj, field_name)

Dynamically creating Django models with `type`

I have 20+ MySQL tables, prm_a, prm_b, ... with the same basic structure but different names, and I'd like to associate them with Django model classes without writing each one by hand. So, feeling ambitious, I thought I'd try my hand at using type() as a class-factory:
The following works:
def get_model_meta_class(prm_name):
class Meta:
app_label = 'myapp'
setattr(Meta, 'db_table', 'prm_%s' % prm_name)
return Meta
prm_class_attrs = {
'foo': models.ForeignKey(Foo),
'val': models.FloatField(),
'err': models.FloatField(blank=True, null=True),
'source': models.ForeignKey(Source),
'__module__': __name__,
}
###
prm_a_attrs = prm_class_attrs.copy()
prm_a_attrs['Meta'] = get_model_meta_class('a')
Prm_a = type('Prm_a', (models.Model,), prm_a_attrs)
prm_b_attrs = prm_class_attrs.copy()
prm_b_attrs['Meta'] = get_model_meta_class('b')
Prm_b = type('Prm_b', (models.Model,), prm_b_attrs)
###
But if I try to generate the model classes as follows:
###
prms = ['a', 'b']
for prm_name in prms:
prm_class_name = 'Prm_%s' % prm_name
prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
globals()[prm_class_name] = prm_class
###
I get a curious Exception on the type() line (given that __module__ is, in fact, in the prm_class_attrs dictionary):
File ".../models.py", line 168, in <module>
prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
File ".../lib/python2.7/site-packages/django/db/models/base.py", line 79, in __new__
module = attrs.pop('__module__')
KeyError: u'__module__'
So I have two questions: what's wrong with my second approach, and is this even the right way to go about creating my class models?
OK - thanks to #Anentropic, I see that the items in my prm_class_attrs dictionary are being popped away by Python when it makes the classes. And I now have it working, but only if I do this:
attrs = prm_class_attrs.copy()
attrs['Meta'] = get_model_meta_class(prm_name)
prm_class = type(prm_class_name, (models.Model,), attrs)
not if I set the Meta class as an attribtue with
setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
I don't really know why this is, but at least I have it working now.
The imediate reason is because you are not doing prm_class_attrs.copy() in your for loop, so the __modules__ key is getting popped out of the dict on the first iteration
As for why this doesn't work:
setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
...it's to do with the fact that Django's models.Model has a metaclass. But this is a Python metaclass which customises the creation of the model class and is nothing to do with the Meta inner-class of the Django model (which just provides 'meta' information about the model).
In fact, despite how it looks when you define the class in your models.py, the resulting class does not have a Meta attribute:
class MyModel(models.Model):
class Meta:
verbose_name = 'WTF'
>>> MyModel.Meta
AttributeError: type object 'MyModel' has no attribute 'Meta'
(You can access the Meta class directly, but aliased as MyModel._meta)
The model you define in models.py is really more of a template for a model class than the actual model class. This is why when you access a field attribute on a model instance you get the value of that field, not the field object itself.
Django model inheritance can simplify a bit what you're doing:
class GeneratedModelBase(models.Model):
class Meta:
abstract = True
app_label = 'myapp'
foo = models.ForeignKey(Foo)
val = models.FloatField()
err = models.FloatField(blank=True, null=True)
source = models.ForeignKey(Source)
def generate_model(suffix):
prm_class_name = 'Prm_%s' % prm_name
prm_class = type(
prm_class_name,
(GeneratedModelBase,),
{
# this will get merged with the attrs from GeneratedModelBase.Meta
'Meta': {'db_table', 'prm_%s' % prm_name},
'__module__': __name__,
}
)
globals()[prm_class_name] = prm_class
return prm_class
prms = ['a', 'b']
for prm_name in prms:
generate_model(prm_name)
You can use ./manage.py inspectdb
This will print out a python models file for the DB you're pointing at in your settings.py
Documentation
EDIT
For dynamic models, try django-mutant or check out this link
For anyone still wondering how to do this, I took the genius answer from #Anentropic and made it work with some modifications.
I also changed the db table name to something more like django uses to name the tables of the models ("appname_lowercaseclassname") by stripping all non alphabetic characters from the class name and converting the resulting string to lower case.
I Tested it on Django 2.2.6
def generate_model(class_name):
clean_name_for_table = ''.join(filter(str.isalpha, class_name)).lower() # Could also use regex to remove all non alphabetic characters.
db_table_name = f"{__package__}_{clean_name_for_table}"
prm_class = type(
class_name,
(BaseClass,),
{
# this will get merged with the attrs from GeneratedModelBase.Meta
'Meta': type("Meta",(),{'db_table':db_table_name}),
'__module__': __name__,
}
)
globals()[class_name] = prm_class
return prm_class

ManyToMany Relation using Django

I have the following code on models.py
class Movie(models.Model):
mov = models.CharField(max_length = 500)
def __str__(self):
return self.mov
class Atrib(models.Model):
atrib = models.CharField(max_length = 500)
def __str__(self):
return self.atrib
class Dict(models.Model):
atrib = models.ForeignKey(Atrib)
dictionary = models.ManyToManyField(Movie, related_name = 'dictionary')
I want to load a defaultdict like this: {attribute1:[movie1, movie2, ...], atribute2:[movie2, movie3,...] ... } using Django.
First of all, I added all the attributes of the dictionary using the Atrib class.
Now I need to add, for every attribute, the related movies. I tried to do the following, considering only one attribute (just testing):
attribute1 = Atrib(atrib = atribs)
for m in dictionary[attribute]:
m = Movie(mov = m)
m.save()
attribute1.dictionary.add(m)
It shows me the following error:
AttributeError: 'Atrib' object has no attribute 'dictionary'.
Am I doing the models correctly for this type of dictionary? And why is this error occurring? I am new in Django and I am a little lost!
Thanks in advance!
First of all you need to save attribute1 = Atrib(atrib = atribs)
do attribute1.save()
Next, after Attribute object creation you don't have Dictionary object related to it.
You need to create it first, set it to this object and then you will have ability to operations you want.
Note: to access attribute.dict you need OneToOne relationship, not ForeignKey.

how to set foreign key during form completion (python/django)

During form processing I'd like to be able to set a foreign key field on a model object without the user having to select the key from a dropdown.
For instance:
#models.py
class AAA(models.Model):
some_field = models.TextField()
class BBB(models.Model):
another_field = models.TextField()
key_field = models.ForeignKey('AAA')
The user will navigate from a view showing an instance of 'AAA' to a create_object style view that will create an instance of 'BBB' given a parameter referring to 'AAA'. The foreign key is set in code to point back to the 'AAA' instance.
The django comments framework seems to do this but I can't figure out how.
Any ideas? I'm sure it should be quite simple.
You can exclude the key_field from your model form, save with commit=False, then set key_field in your view before saving to the database.
class BBBForm(forms.ModelForm):
class Meta:
model = BBB
exclude = ("key_field",)
def create_view(request, **kwargs):
if request.method == "POST":
aaa = # get aaa from url, session or somewhere else
form = BBBForm(request.POST)
if form.is_valid():
bbb = form.save(commit=False)
bbb.key_field = aaa
bbb.save()
return HttpResponseRedirect("/success-url/")
...
As the user creates a BBB via an instance of AAA, this should be reflected in the URL, i.e., your "create_object style view" will get a parameter identifying an AAA object. You can use it to get the object from the database and create your BBB object accordingly:
from django.shortcuts import get_object_or_404
def create_bbb_view(request, aaa_id):
a = get_object_or_404(AAA, id=aaa_id)
form = MyBBBCreationForm(request.POST) # or similar code
if form.is_valid():
b = BBB.objects.create(key_field=a) # plus other data from form
# ...
(You could also set key_field to aaa_id directly, but it's probably a good idea to check if the object exists.)

Categories