Django - get a field's name from within its init function - django

Consider the following code:
class MyManyToManyField(models.ManyToManyField):
def __init__(self,*args,**kwargs):
field_name = ?!?
kwargs["related_name"] = "%(app_label)s.%(class)s." + field_name
super(MetadataManyToManyField,self).__init__(*args,**kwargs)
class MyModelA(models.Model):
modelAField = MyManyToManyField("MyModelB")
class MyModelB(models.Model):
pass
Is there any way for me to access the name of the field from within my overloaded init function? I want the related_name of modelAField to wind up being "MyAppName.MyModelA.modelAField".
I've thought about just passing it as a kwarg:
modelAField = MyManyToManyField("MyModelB",related_name="modelAField")
and then using it in init:
field_name = kwargs.pop("related_name",None)
if not field_name:
raise AttributeError("you have to supply a related name!")
But I'm hoping for something a bit nicer.
Thanks.

Use contribute_to_class
As has been mentioned before, a field object has to be instantiated, by calling its __init__ function, before it can be assigned to a variable within the class.
However, while the model is being constructed, Django looks for a method on the field called contribute_to_class. If it is present, it will be called like this:
new_field.object.contribute_to_class(ModelClass, field_name)
If you override this method in your custom field class, you can perform whatever initialisation is required at the point where the field is added to the model.

I don't think this is possible. The object MyManyToManyField is going to be instantiated before it's assigned to a variable, so it's too early to be able to do anything clever.
There might be same magic in Django's metaclass for models which interacts with the fields, it might be possible to hijack this interaction and put you logic there. But I'm just speculating at this point.

Passing as a kwarg doesn't seem that cumbersome.
I suspect that Python can't decide what the name of an instance is, since an object can have many reference pointers.
Eg
x = YourClass()
y = x
Is the name of the instance of YourClass 'x' or 'y'? Or is it just an instance of YourClass?

Related

Is it possible to edit the 'field' class variable in Django CBV using URL args?

I am trying to use a CreateView to create a profile object. Depending on the type of profile, though, I want to show a different set of fields. Right now I am passing the type of profile through the url.
How would I go about accessing this argument to filter the types of fields? I know how to do so in something like a get() function in a CBV, but not in the body of the class itself. I also do not know if there is an appropriate function to do this in and have not been able to find a solution in the docs.
Thank you in advance for any help, and I am sorry if this question has an obvious answer that I have missed.
You would need to override some method. Potential methods that can work are get_form_class, get_form, etc. Try overriding get_form:
class MyView(CreateView):
model = SomeModel
fields = None
def get_form(self):
self.fields = ['field1', 'field2'] # set this according to your conditions
# the keyword arguments passed to the view can be accessed by using self.kwargs.get('<key_name>')
return super().get_form()

Concise way of getting or creating an object with given field values

Suppose I have:
from django.db import models
class MyContentClass(models.Model):
content = models.TextField()
another_field = models.TextField()
x = MyContentClass(content="Hello, world!", another_field="More Info")
Is there a more concise way to perform the following logic?
existing = MyContentClass.objects.filter(content=x.content, another_field=x.another_field)
if existing:
x = existing[0]
else:
x.save()
# x now points to an object which is saved to the DB,
# either one we've just saved there or one that already existed
# with the same field values we're interested in.
Specifically:
Is there a way to query for both (all) fields without specifying
each one separately?
Is there a better idiom for either getting the old object or saving the new one? Something like get_or_create, but which accepts an object as a parameter?
Assume the code which does the saving is separate from the code which generates the initial MyContentClass instance which we need to compare to. This is typical of a case where you have a function which returns a model object without also saving it.
You could convert x to a dictionary with
x_data = x.__dict__
Then that could be passed into the object's get_or_create method.
MyContentClass.objects.get_or_create(**x_data)
The problem with this is that there are a few fields that will cause this to error out (eg the unique ID, or the _state Django modelstate field). However, if you pop() those out of the dictionary beforehand, then you'd probably be good to go :)
cleaned_dict = remove_unneeded_fields(x_data)
MyContentClass.objects.get_or_create(**cleaned_dict)
def remove_unneeded_fields(x_data):
unneeded_fields = [
'_state',
'id',
# Whatever other fields you don't want the new obj to have
# eg any field marked as 'unique'
]
for field in unneeded_fields:
del x_data[field]
return x_data
EDIT
To avoid issues associated with having to maintain a whitelist/blacklist of fields you, could do something like this:
def remove_unneeded_fields(x_data, MyObjModel):
cleaned_data = {}
for field in MyObjModel._meta.fields:
if not field.unique:
cleaned_data[field.name] = x_data[field.name]
return cleaned_Data
There would probably have to be more validation than simply checking that the field is not unique, but this might offer some flexibility when it comes to minor model field changes.
I would suggest to create a custom manager for those models and add the functions you want to do with the models (like a custom get_or_create function).
https://docs.djangoproject.com/en/1.10/topics/db/managers/#custom-managers
This would be the cleanest way and involves no hacking. :)
You can create specific managers for specific models or create a superclass with functions you want for all models.
If you just want to add a second manager with a different name, beware that it will become the default manager if you don't set the objects manager first (https://docs.djangoproject.com/en/1.10/topics/db/managers/#default-managers)

Delete field from standard Django model

NOTE: this was asked before AbstractUser existed, which is probably what you'd want to use these days.
Basically I would like to delete the default email field from the default Django User class...
class MyUser(User):
field = models.CharField(max_length = 10)
a = 'hello'
def b(self):
print 'world'
del User.email
del MyUser.email
del Myuser.field
All these give AttributeError. Deleting methods or attributes in the following way though works fine:
del MyUser.a
del MyUser.b
So I'm curious why this doesn't work; what type of things are these model fields?
Another thing I tried was overwriting email by creating an email = None in MyUser but that didn't work either (and would be slightly more ugly).
Thanks in advance!
P.s. If you are wondering why; it's more for curiousity than that it is really necessary for the application... But I think it's good not to have unused columns in database.
P.p.s. I don't want to change the Django files to manually remove 'email' from user.
EDIT: Follow-up question here (for those who want to do the same thing) Before syncdb, delete field from standard Django model
As you've discovered, model fields aren't actually class attributes, even though they appear to be.
Models are constructed by some very clever but complicated hacking around with metaclasses. When a model class definition is executed (the first time its models.py is imported), the metaclass runs through all the field definitions for that model, and calls the contribute_to_class method of each one. This ends up putting the actual fields into the new class's _meta.fields property. So the model class itself doesn't have the fields as direct properties.
Then, when a model instance is actually instantiated, Django takes that list and directly sets the new instance's attributes, using either the arguments to the constructor or the default values supplied by the fields. So, the value that's accessed via the instance has no actual field code behind it: it's a simple instance attribute.
Anyway, the upshot of all this is that to remove a field from a model definition you just need to delete it from the Model._meta.fields list.
Since Model._meta.fields is an immutable list, you won't be able to change it directly.
You can, however, modify local_fields like this:
def remove_field(model_cls, field_name):
for field in model_cls._meta.local_fields:
if field.name == field_name:
model_cls._meta.local_fields.remove(field)
remove_field(User, "email")

Django - How to iterate and inspect each choice for a ModelChoiceField in the template code

I have a form (edited for brevity) as follows:
class InteractionForm(forms.Form):
def __init__(self, *args, **kwargs):
# Each object within this queryset is a model object of type InteractionChoice
choices_qs = interaction.interactionchoice_set.all()
self.fields['choices'] = forms.ModelChoiceField(
widget=forms.RadioSelect(),
queryset=choices_qs,
The InteractionChoice model looks like:
class InteractionChoice(models.Model):
interaction = models.ForeignKey(Interaction)
name = models.CharField(max_length=255)
is_answer = models.BooleanField(default=False)
An instance of InteractionForm is passed from a view to a template and rendered via:
{{ form.choices }}
My question is whether there is a way to iterate over each choice in my template and access one of its properties -- specifically, the is_answer property defined in InteractionChoice. The purpose being to customize how a choice is displayed if it is indeed the answer. More specifically, if is_answer is True, I'd possibly change the class attribute on the <label> for that choice.
Perhaps, I'm approaching this problem from the wrong direction. If anyone has pointers for alternative ideas, I'd be happy to hear them.
Thanks in advance.
Update 1:
Thinking about this more after #rczajka's response, I don't believe I can achieve what I'm hoping to do in the template code. Instead, if the purpose is to modify the tag's class attribute, I should perhaps be looking to subclass and override certain methods in forms.widgets.RadioInput, forms.widgets.RadioFieldRenderer, and forms.widgets.RadioSelect. I'll dig into this more.
I came up with one solution that addresses this problem. It's hackish, to say the least, but it's the only approach I've thought of that works thus far without a lot of back-end changes to my existing design.
My approach stemmed from this article on subclassing `RadioFieldRenderer' and 'RadioSelect'.
In the __unicode__ method for an InteractionChoice model, I return:
return self.name + "_" + str(self.is_answer)
which is the value used for a radio button's label (amongst other things). I then subclassed forms.widgets.RadioInput, forms.widgets.RadioFieldRenderer, and forms.widgets.RadioSelect.
For the custom RadioInput class, I overrode its __unicode__ method to include logic to append a class string – whose value is ultimately dictated by the string returned from the unicode method in InteractionChoice – to the <label> tag string it returns.
For the custom RadioFieldRenderer class, I overrode __iter__ and __getitem__ to use the custom RadioInput class.
For the custom RadioSelect class, I overrode the renderer property to use my custom radio field renderer.
This is obviously far from an ideal solution. Hopefully, a better one will arise.
I found a similar problem but solved it in a different way.
How to get ModelChoiceField instances in the template
Iterating over the field's queryset property.
You should subclass ModelChoiceField and override label_from_instance. It says so here: https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield:
The unicode method of the model will be called to generate string representations of the objects for use in the field's choices; to provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object, and should return a string suitable for representing it.

Instantiate model instance with manytomany field in Django

I have a method that works, but it seems very clumsy, and I would think there is a better way to do this.
I have a Model that relates a user on my site (a twitter clone for learning purposes) to a list of other users.
Right now when I create a new user, I want to initialize that list with the user as a member of the list.
My model is:
class FollowerList(models.Model)
follower = models.ForeignKey(User,related_name="follower")
followed = models.ManyToManyField(User,related_name="followed")
The code in my view that I'm using right now is
user = User.objects.get(username=uname)
flst = FollowerList()
flst.follower = user
flst.save()
flst.followed.add(user)
flst.save()
It seems to me like there should be a method for creating this without calling save() twice, but I can't seem to find it in the docs or anywhere else.
You don't need to call save after the many2many.add()
You could also shorten the code to 2 lines:
flst = FollowerList.objects.create(follower=user)
flst.followed.add(user)
Yuji's answer is correct. You can not add an object to a M2M field until it has been saved. I wanted to mention a shorter way to create instances though.
user = User.objects.get(username=uname)
flst = FollowerList(follower=user) #use key word args to assign fields
flst.save()
flst.followed.add(user)
# don't need to save after adding an object to many to many field.
I find that syntax slightly nicer than creating an empty instance and assigning fields. Though the objects.create() method (mentioned by Yuki) is nicer still.
A late answer to this: you could also override the constructor (__init__) as follows:
class FollowerList(models.Model):
follower = models.ForeignKey(User,related_name="follower")
followed = models.ManyToManyField(User,related_name="followed"
def __init__(*args, followed=[], **kwargs):
super(FollowerList, self).__init__(*args, **kwargs)
self.save()
for user in followed:
self.followed.add(user)
ie here I've explicitly handled the followed keyword argument in the __init__ function, while passing all other args and kwargs on to the default constructor.
The call to save makes sure that the object has been registered and can thus be used in an m2m relationship.
This then allows you to do create FollowerList with one line, eg
flst = FollowerList(follower=user, followed=[user,])
Alternatively, as pointed out by Johannes, saving a model in the __init__ is not expected. The preferred approach would be to create a Manager method - see here for details: https://docs.djangoproject.com/en/1.9/topics/db/managers/
and then to create a FollowerList:
fl = FollowerList.objects.create(*args, followed, **kwargs)