Updating related objects django rest framework - python

In my django project, I have 2 relevant models:
class User(AbstractUser):
email = models.EmailField(
verbose_name='e-mailaddress',
max_length=255,
unique=True)
# other props not important
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
#other props not important
For both creating and retrieving, it is convenient to have User as a nested object in the serializer:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = '__all__'
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.save()
# because of reasons, an empty profile is created on initial save. This works, trust me
d, _ = Profile.objects.update_or_create(user=user, defaults=validated_data)
d.save()
return d
HOWEVER. One action that I want to be possible as well is to update both properties of the Profile and the User at the same time. When doing this with my serializer, serializer.is_valid() fails as the provided email is already present in the database. This also indicates that, even if the validation passes (i.e. because of an updated email address), a new User object will be created and coupled to the Profile. So my question is:
How do I make the validation of a serializer check if the edit of a nested object is valid rather than checking if a new one can be created?

you can update the properties of the profile and its related user by doing something similar.
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = '__all__'
def update(self, instance, validated_data):
# update the profile-related fields here.
# the other properties which you didn't mention.
...
profile = instance
# get the profile object
...
instance.save()
user = validated_data.get('user')
# get the related user.
# update the user properties here, like:
user.email = 'UPDATED_EMAIL'
user.save() # you need to save the user as well as the profile!
return instance

I managed to find something of a workaround on my own. Using the source property of serializer fields, one is able to add certain properties from related objects to the serializer. In my case, I was able to add the following lines to the ProfileSerializer for the functionalities I need:
first_name = serializers.CharField(max_length=30, source='user.first_name', required=False)
last_name = serializers.CharField(max_length=150, source='user.last_name', required=False)
email = serializers.CharField(max_length=255, source='user.email')
There are more properties to User, but for my current requirements I don't need them and this works fine (though it would be tedious to add such a line for all properties if you need all attributes). It does require a custom implementation of both create and update functions, but I assume one would need those anyway. Personally I used:
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.save()
instance = ...
return instance
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
for k,v in user_data.items():
setattr(user, k, v)
user.save()
...
return instance
Note: django-rest automatically groups properties with a shared source object into a nested object: {'user': {'email': ..., 'first_name': ..., 'last_name': ...}, 'phone_number': ...} (where phone_number is a profile property)

Related

Bookmarking function django/python

I'm looking to create a model for users to bookmark a recipe. I have the below:
models.py
class RecipeBookmark(models.Model):
recipe = models.ForeignKey(
Recipe, on_delete=models.PROTECT, related_name="bookmarks"
)
bookmarked_by = models.ForeignKey(User, on_delete=models.PROTECT)
bookmarked_at = models.DateTimeField(auto_now_add=True)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ["username", "email", "date_joined"]
class RecipeBookmarkSerializer(serializers.ModelSerializer):
bookmarked_by = UserSerializer(read_only=True)
class Meta:
model = models.RecipeBookmark
fields = ["recipe", "bookmarked_by", "bookmarked_at"]
def create(self, validated_data):
request = self.context["request"]
ModelClass = self.Meta.model
instance = ModelClass.objects.create(
**validated_data, **{"bookmarked_by": request.user}
)
return instance
views.py
#permission_classes([IsAuthenticated])
class RecipeBookmarkView(generics.CreateAPIView):
queryset = models.RecipeBookmark.objects.all()
serializer_class = RecipeBookmarkSerializer
urls.py
path("recipes/bookmarks/", PublishedRecipeBookmarkView.as_view()),
I want to perform a lookup, given the recipe id through a POST request, to add the user to the bookmarks field, if the user already exists in the bookmarks field, to remove that user form the field (remove the bookmark). Many users can bookmark a given recipe.
Also, How can a lookup be performed to return recipes that a logged in user has bookmarked via an api endpoint?
Current error with get_or_create():
Error: Internal Server Error
Response body
Download
AttributeError at /api/recipes/bookmarks/
Got AttributeError when attempting to get a value for field recipe on serializer RecipeBookmarkSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the tuple instance.
Original exception text was: 'tuple' object has no attribute 'recipe'.
If you want your serializer to ensure that only one bookmark is created per user per recipe, you can use get_or_create:
def create(self, validated_data):
request = self.context["request"]
ModelClass = self.Meta.model
instance = ModelClass.objects.get_or_create(
**validated_data, **{"bookmarked_by": request.user}
)
return instance
If the bookmark is already present, it will just grab it and return.
Also, How can a lookup be performed to return recipes that a logged in user has bookmarked via an api endpoint?
To support this, you can define ListCreateAPIView to your view and override the queryset like so:
class RecipeBookmarkView(generics.ListCreateAPIView):
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(bookmarked_by=self.request.user)
This will then support getting all the RecipeBookmark that is owned by the current user via GET requests on recipes/bookmarks/

Custom user model doesn't create objects

I have created a CustomUser model and it has a one to relationship with my two other models.
class User(AbstractUser):
is_learner = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
class Teacher(models.Model):
#teacher_name = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
class Learner(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
They do not show up when I try to create learner and teacher objects in their respective databases, as shown:
class LearnerSignUpForm(UserCreationForm):
email = forms.EmailField()
class Meta(UserCreationForm.Meta):
#User = CustomUser
model = CustomUser
fields = ["username", "email", "password1", "password2"]
#fields = UserCreationForm.Meta.fields + ("username", "email", "password1", "password2")
#transaction.atomic
def save(self, *args, **kwargs):
user = super().save(commit=False)
user.is_learner = True
user.save()
learner = Learner.objects.create(user=user)
return user
How do I get it to save in learner and teacher tables respectively?
Welcome to StackOverflow!
It is worth pointing that using model.objects.create(...) you're implicitly telling Django that you want to create the object with the specific values you give and save it. check the docs page here
If you wish to save an object for which has associated items, you can create a form and use a ModelChoiceField and provide the model and default choices using the choices kwarg. An example of a choices value would be YourUserModel.objects.all() However, I think in your case you may not want to give the user the freedome to make this choice, in which it would be correct to override the save() method of your form and create the intended logic.
Secondly, I have used Django for quite some time and never seen a Model.Meta class used in this way so forgive me if I'm mistaken, but I also think you need have your save() method directly on your LearnerSignupForm and not on the Meta class.
Thirdly, if you setup your forms correctly, using the correct types of fields, Django forms will deal with all the messy stuff for you and complain at you when you do something wrong (usually gracefully).
Lastly, I would highly recommend having a read through the docs page for creating new objects
Free code:
class LearnerSignUpForm(UserCreationForm):
email = forms.EmailField()
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ["username", "email", "password1", "password2"]
#transaction.atomic
def save(self, *args, **kwargs):
user = super().save(commit=False)
user.is_learner = True
# create the relationship between the user and learner
learner = user.learner_set.create(user=user)
# user.learner_set.add(learner) # usually this way with FK
user.save()
return user # should this return the Learner object?
One final bit of advice:
Try to make your code as readable as possible! There's no reason that code can't be functional and beautiful! And where possible, you should make you class names and variable names as appropriate as possible, I would for example maybe use Student instead of Learner

Django set_password no attribute

I'm trying to use the set_password() function but this error
'Member' object has no attribute 'set_password'
comes up when I use it. If I take out the set_password() function the password is stored in the database but without being hashed.
view.py
user = Member(username=u, password=p, email=e, security=s)
user.set_password(p)
user.save()
models.py
class Member(models.Model):
username = models.CharField(max_length=16,primary_key=True)
password = models.CharField(max_length=16)
email = models.CharField(max_length=325)
security = models.CharField(max_length=16)
profile = models.OneToOneField(Profile, null=True)
following = models.ManyToManyField("self", symmetrical=False)
from_member_id = models.CharField(max_length=16)
def __str__(self):
return self.username
The set_password function is not automatically provided by models.Model.
You have to define it by yourself or derive Member from django Usermodel
The documentation on providing your own user model is quite clear and comprehensive. Among other things, your model must be a subclass of AbstractBaseUser, which is what provides the set_password method.
Also note that 16 characters is not nearly long enough to store a hashed, salted password.
As the error message tells you, the method set_password is not defined.
Either you implement it yourself, or (better) create your Member model by subclassing django's AbrstactBaseUser:
class MyUser(AbstractBaseUser):
username = models.CharField(max_length=16,primary_key=True)
password = models.CharField(max_length=16)
email = models.CharField(max_length=325)
security = models.CharField(max_length=16)
profile = models.OneToOneField(Profile, null=True)
following = models.ManyToManyField("self", symmetrical=False)
from_member_id = models.CharField(max_length=16)
USERNAME_FIELD = 'username'
You can find more about custom user models in the django docs
Not sure if your doing this but it is probably easier to make the model with OneToOneField(User) and give it additional fields. You just need to remember to save in the new fields or the fields wont show up when u call.
Where you set the user_form=Member(request.POST)
user = user_form.save()
user.set_password(user.password)
profile = user.userprofile
profile.bio = request.POST['bio']
profile.save()

Django register extended user

I'm trying to create and manage a custom user in django.
I saw there are two possibilities, and i've chosen to extend (not create a new auth).
Models
models.py
class Artists(models.Model):
user = models.OneToOneField(User)
artist_image = models.ImageField(null=True, blank=True, upload_to="/artist_image/")
def __str__(self):
return 'Profil de {0}'.format(self.username)
def get_absolute_url(self):
return reverse('artist-details', kwargs={'pk': self.pk})
As i read in doc, I just make a OneToOne field with the User class of django auth models, so I can access method and properties, such as username, email, on my own user (here Artists).
form.py
class CreateArtistForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'email')
def save(self, commit=True):
user = super(CreateArtistForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Here I extend UserCreationForm to prepare a form a little different (I want to have email field on my register form).
But here is my question : I first tried with
class Meta:
model = Artists
fields = ('user.username', 'user.email')
But I the error fields unknown in model Artist.
So I tried just with username and email and same error.
So I changed the model = Artists to User, and it works fine.
But now how i register my Artist Object when the user is saved?
Do I have to make something like (in save()):
artist = Artists()
artist.user = user
artist.save()
Or override create_user()?
I'm quite lost here and i'm looking docs and questions not able to find something because most of example people define their own auth.
Thanks in advance
Besta
edit : i'm using django 1.8.2 and python 3.4

Creating & Updating UserProfiles in Django

Django newbie here stumbling my way around the docs. I'm trying to create a user profile using Django's "UserProfiles", but I'm having a little trouble with figuring out the proper way to set the code based on Django docs.
Here's my code, based on the docs. (The create_user_profile is 100% from the docs).
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.CharField(max_length = 100)
website = models.CharField(max_length=50)
description = models.CharField(max_length=255)
fullName = models.CharField(max_length=50)
email = models.EmailField(max_length = 100, blank = False)
created = models.DateTimeField(auto_now_add=True)
private = models.BooleanField()
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What's the -proper- way to set and save these fields?
For example, if I have both the User and UserProfile models in one form (in a registration form, for example), how would I first create, then update all of this, before finally saving?
how would I first create, then update all of this, before finally saving
These aren't separate steps. When you create or update a record in Django, you are saving it to the database.
For the registration form, I'd recommend you set it up as a ModelForm on User records, then specify the additional fields you want to save to the profile and save them separately in the save function, like so...
class RegistrationForm(forms.ModelForm):
location = forms.CharField(max_length=100)
# etc -- enter all the forms from UserProfile here
class Meta:
model = User
fields = ['first_name', 'last_name', 'email', and other fields in User ]
def save(self, *args, **kwargs):
user = super(RegistrationForm, self).save(*args, **kwargs)
profile = UserProfile()
profile.user = user
profile.location = self.cleaned_data['location']
# and so on with the remaining fields
profile.save()
return profile
You could call profile.user.save() and after it profile.save() when you need to save data from registration form.

Categories