Cannot use a string in Django ManyToManyField through value - python

(Apologies if I'm missing some critical info to help diagnose this -- I'm new to Python and Django.)
Django complains when I try to use a string in my ManyToManyField through attribute:
File "/vagrant/flamingo_env/lib/python3.4/site-packages/django/db/models/fields/related.py", line 1366, in _check_relationship_model
for f in through._meta.fields:
AttributeError: 'str' object has no attribute '_meta'
Usage:
class Voter(models.Model):
# ...
addresses = models.ManyToManyField(Address, through='voter_addresses', through_fields=('voter_id', 'address_id'))
The error goes away if I create a Through model:
class VoterAddress(models.Model):
voter_id = models.ForeignKey(Voter)
address_id = models.ForeignKey(Address)
class Meta:
db_table = 'voter_addresses'
But of course then it complains that Voter hasn't been defined--and I can't simply change the order, or else VoterAddress won't have been defined either.
And in every single example I have seen the basic string version used. What's going on?

You need to fix the name passed to your through
addresses = models.ManyToManyField(Address, through='VoterAddress')
It has to be the exact name of the custom throughmodel
You will not be required to create a throughmodel if you do not pass the through argument. Django will create and manage one for you

To fix your ordering issue you can give a string value to the ForeignKey class.
class VoterAddress(models.Model):
voter_id = models.ForeignKey("Voter")
address_id = models.ForeignKey("Address")
class Meta:
db_table = 'voter_addresses'
This is a way to define foreignkeys to Models that haven't been defined in the file yet.
If you change this Django won't complain that Voter hasn't been defined.

Related

Set ManyToManyField with particular users from another model's ManyToMany Field

I am building a simple class group app in which I am trying to add particular users from another model's ManyToFieldField to a new model's ManyToFieldField.
class ClassGroup(models.Model):
admins = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='admins')
members = models.ManyToManyField(settings.AITH_USER_MODEL)
title = models.CharField(max_length=9999, default='')
class ClassGroupInvite(models.Model):
class_group = models.ForeignKey(ClassGroup, on_delete=models.CASCADE)
invite_receiver = models.ManyToManyField(class_group.admins.all())
invite_sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
As you can see that I am filtering (send request only to class group admins) in ClassGroupInvite with setting ManyToManyField with ClassGroup.admins
But when I try this then it is showing
ManyToManyField(<django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x000001CE78793280>) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string 'self'
I also read the documentation about it, But I didn't find anything about defining it.
then I tried using ClassGroup.admins.all then it showed
AttributeError: 'ManyToManyDescriptor' object has no attribute 'all'
I have tried many times but it is still not working, Any help would be much Appreciated. Thank You in Advance.

Reference ManyToMany Field in Django Model Method

I am attempting to reference the ManyToMany connection between Units and Add-Ons in my code to enable a method that provides the price, but I seem to be unable to reference the relationship in a Model method, can somebody point me in the right direction?
class Unit(models.Model):
class Meta:
permissions = (
('generate_unit_csv', 'can generate unit csv'),
)
unique_together = ['building', 'unit_number', 'property']
add_ons = models.ManyToManyField(
'properties.RentalAddOns', related_name='add_on_units')
...
def get_add_on_price(self):
total_price = 0
# there is no self.add_on_units or self.add_ons
for add_on in self.add_on_units:
total_price += add_on.price
return total_price
When I call the method I get an Attribute Error:
'Unit' object has no attribute 'add_on_units'
When I simple use self.add_ons I get:
'ManyRelatedManager' object is not iterable
As you've defined the field in this model, you need to refer to it as such. Also, you need to use the all() method on the resulting manager object to get all the related objects so that you can iterate over them:
self.add_ons.all()
And from the RentalAddOns model instance, you can refer all the Unit model instances as:
self.add_on_units.all()

Django 1.11 One-to-Many relationship no related set

So I have two models:
class UserData(models.Model):
""" Holds basic user data. """
id = models.IntegerField(primary_key=True, editable=False) # id is taken from data.
class ConsumptionTimePoint(models.Model):
""" Individual consumption time points with a One-to-Many relationship with UserData """
user_data = models.ForeignKey(UserData, on_delete=models.CASCADE)
And when I try and test them by creating them both, and their relationship in a test:
def test_basic_model_creation(self):
user_data_object = UserData.objects.create(id=1)
user_data_object.save()
consumption_time_point_object = ConsumptionTimePoint.objects.create(user_data=user_data_object)
consumption_time_point_object.save()
self.assertIsNotNone(consumption_time_point_object.user_data)
self.assertEquals(1, len(user_data_object.consumption_time_point_set.all()))
I get the following error:
AttributeError: 'UserData' object has no attribute 'consumption_time_point_set'
But from my understanding that's the correct way to get the set. Have I misnamed something? Or is this a testing issue?
To get the related queryset the class name is lowercased and _set is appended. Try consumptiontimepoint_set
You can also set the reverse relation name manually by using the related_name parameter.

how to access may-to-many relationship in django?

I have two model
class ClassProfile(models.Model):
classname = models.CharField(max_length=100, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User)
class = models.ManyToManyField('ClassProfile')
I try to get all the famulymember like this
class = Class.objects.get(pk=1)
members = class.userprofile_set.all()
this will rasie an error
'ClassProfile' object has no attribute 'userprofile_set'
what's wrong with my code?
What makes you think django uses CamelCase anywhere? By default, the reverse accessor is lowercaseclsname_set
So class.userprofile_set.all() should do it.
Aside from the fact that it is not a good idea to name a variable class, I think you have a typo in this line:
class = Class.objects.get(pk=1)
You probably meant:
class = ClassProfile.objects.get(pk=1)

In Django - Model Inheritance - Does it allow you to override a parent model's attribute?

I'm looking to do this:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
This is the version I would like to use (although I'm open to any suggestion):
http://docs.djangoproject.com/en/dev/topics/db/models/#id7
Is this supported in Django? If not, is there a way to achieve similar results?
Updated answer: as people noted in comments, the original answer wasn't properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.
A solution is to create an abstract model representing a "Place", eg. AbstractPlace, and inherit from it:
class AbstractPlace(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class Place(AbstractPlace):
pass
class LongNamedRestaurant(AbstractPlace):
name = models.CharField(max_length=255)
food_type = models.CharField(max_length=25)
Please also read #Mark answer, he gives a great explanation why you can't change attributes inherited from a non-abstract class.
(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn't possible.)
Original answer
Since Django 1.10 it's
possible!
You just have to do what you asked for:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
No, it is not:
Field name “hiding” is not permitted
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.
If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.
I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As #lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.
Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it's required in all forms. This doesn't guarantee database integrity if other applications use it, and doesn't work the other way around (if you want to make username not required).
See https://stackoverflow.com/a/6379556/15690:
class BaseMessage(models.Model):
is_public = models.BooleanField(default=False)
# some more fields...
class Meta:
abstract = True
class Message(BaseMessage):
# some fields...
Message._meta.get_field('is_public').default = True
My solution is as simple as next monkey patching, notice how I changed max_length attribute of name field in LongNamedRestaurant model:
class Place(models.Model):
name = models.CharField(max_length=20)
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
Place._meta.get_field('name').max_length = 255
Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:
django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Looks like Django does not support that.
This supercool piece of code allows you to 'override' fields in abstract parent classes.
def AbstractClassWithoutFieldsNamed(cls, *excl):
"""
Removes unwanted fields from abstract base classes.
Usage::
>>> from oscar.apps.address.abstract_models import AbstractBillingAddress
>>> from koe.meta import AbstractClassWithoutFieldsNamed as without
>>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
... pass
"""
if cls._meta.abstract:
remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
for f in remove_fields:
cls._meta.local_fields.remove(f)
return cls
else:
raise Exception("Not an abstract model")
When the fields have been removed from the abstract parent class you are free to redefine them as you need.
This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba
Maybe you could deal with contribute_to_class :
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
def __init__(self, *args, **kwargs):
super(LongNamedRestaurant, self).__init__(*args, **kwargs)
name = models.CharField(max_length=255)
name.contribute_to_class(self, 'name')
Syncdb works fine. I dont tried this example, in my case I just override a constraint parameter so ... wait & see !
I know it's an old question, but i had a similar problem and found a workaround:
I had the following classes:
class CommonInfo(models.Model):
image = models.ImageField(blank=True, null=True, default="")
class Meta:
abstract = True
class Year(CommonInfo):
year = models.IntegerField()
But I wanted Year's inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:
class YearForm(ModelForm):
class Meta:
model = Year
def clean(self):
if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
raise ValidationError("Please provide an image.")
return self.cleaned_data
admin.py:
class YearAdmin(admin.ModelAdmin):
form = YearForm
It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).
Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:
def clean_town(self):
town = self.cleaned_data["town"]
if not town or len(town) == 0:
raise forms.ValidationError("Please enter a town")
return town
You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:
def clean(self):
"""
Make sure that email field is unique
"""
if MyUser.objects.filter(email=self.email):
raise ValidationError({'email': _('This email is already in use')})
The error message is then captured by Form field with name "email"

Categories