0

I have a user model with a ManyToMany relationship with another Model

class User(AbstractUser):
    countries = models.ManyToManyField(Country, blank=True)
    count = models.IntegerField(blank=True, default=0)

    def save(self, *args, **kwargs):
        # Must save model before Many To Many relationship can be used.
        super(User, self).save(*args, **kwargs)
        self.count = self.countries.count()
        super(User, self).save(*args, **kwargs)

and my country model is a rather large model:

class Country(models.Model):
    '''
    Describes the countries, as well as territories of the world.
    '''
    name = models.CharField(max_length=255, null=True, blank=True)
    top_level_domain = JSONField(null=True, blank=True)
    alpha2code = models.CharField(max_length=255, null=True, blank=True)
    alpha3code = models.CharField(max_length=255, null=True, blank=True)
    calling_codes = JSONField(null=True, blank=True)
    capital = models.CharField(max_length=255, null=True, blank=True)
    alt_spellings = JSONField(null=True, blank=True)
    region = models.CharField(max_length=255, null=True, blank=True)
    subregion = models.CharField(max_length=255, null=True, blank=True)
    population = models.IntegerField(null=True, blank=True)
    latlng = JSONField(null=True, blank=True)
    demonym = models.CharField(max_length=255, null=True, blank=True)
    area = models.FloatField(null=True, blank=True)
    gini = models.FloatField(null=True, blank=True)
    timezones = JSONField(null=True, blank=True)
    borders = JSONField(null=True, blank=True)
    native_name = models.CharField(max_length=255, null=True, blank=True)
    numeric_code= models.CharField(max_length=255, null=True, blank=True)
    currencies = models.ManyToManyField(Currency)
    languages = models.ManyToManyField(Language)
    flag = models.CharField(max_length=255, null=True, blank=True)
    regional_blocs = models.ManyToManyField(RegionalBloc, blank=True)
    cioc = models.CharField(max_length=255, null=True, blank=True)

    def __str__(self):
        return self.name

I'm trying to make a put request from my frontend to update the list of countries associated with (data is sent as an array of objects from React with axios). I got the error "AssertionError: The .update() method does not support writable nested fields by default. Write an explicit .update() method for serializer api.serializers.UserDetailSerializer, or set read_only=True on nested serializer fields." Since I obviously can't make my the countries read_only=True, I'm trying to add an update method to my serializer.

class UserDetailSerializer(UserDetailsSerializer):
    countries = CountrySerializer(many=True)
    count = serializers.IntegerField(read_only=True)

    class Meta:
        model = User
        fields = ('pk', 'username', 'email', 'count', 'countries')

    # Update the instance upon Put Request from frontend.
    def update(self, instance, validated_data):
        instance.countries = validated_data['countries']
        instance.save()
        return instance

This update method gives me the error 'Direct assignment to the forward side of a many-to-many set is prohibited. Use countries.set() instead.'

When I change the update method to

  instance.countries.set(validated_data['countries'])

I get the error 'TypeError: unhashable type: 'collections.OrderedDict''. Starting to feel a bit lost, and like I'm running in circles. How do I write the update method correctly?

edit: What I'm trying to do is just add/remove a Country object to the Users country list. I don't actually want to edit any of the country objects.

my country data being posted would be a list of countries objects looking like this

{
    "id": 6,
    "currencies": [
        {
            "code": "EUR",
            "name": "European Euro",
            "symbol": "€"
        }
    ],
    "languages": [
        {
            "iso639_1": "ca",
            "name": "Catalan",
            "native_name": "Català"
        }
    ],
    "regional_blocs": [],
    "name": "Andorra",
    "top_level_domain": [
        ".ad"
    ],
    "alpha2code": "AD",
    "alpha3code": "AND",
    "calling_codes": [
        "376"
    ],
    "capital": "Andorra la Vella",
    "alt_spellings": [
        "AD",
        "Principality of Andorra",
        "Principat d'Andorra"
    ],
    "region": "Europe",
    "subregion": "Southern Europe",
    "population": 78014,
    "latlng": [
        42.5,
        1.5
    ],
    "demonym": "Andorran",
    "area": 468.0,
    "gini": null,
    "timezones": [
        "UTC+01:00"
    ],
    "borders": [
        "FRA",
        "ESP"
    ],
    "native_name": "Andorra",
    "numeric_code": "020",
    "flag": "https://restcountries.eu/data/and.svg",
    "cioc": "AND"
}

1 Answer 1

1

You get TypeError: unhashable type: 'collections.OrderedDict' because you are trying to assign an object of type collections.OrderedDict to countries, which is a ManyToManyField.

Do the following instead:

def update(self, instance, validated_data):
    country_names = [cdata['name'] for cdata in validated_data['countries']]
    countries = Country.objects.filter(name__in=country_names)
    instance.countries.set(countries)
    return instance
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, that was very easy. .set() has to be a queryset, of the appropriate model?
@peter176 It doesn't have to be a queryset, it could be any iterable (list, set, etc). And yes, you want the elements to be of the appropriate model.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.