Django: Update related object on save? - django

I've "extended" Django's contrib.auth.models "user" like:
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name = 'profile')
date_of_birth = models.DateField(blank=True, null=True)
avatar = models.ImageField(upload_to=get_avatar_path, null=True, blank=True)
friends = models.ManyToManyField(User, related_name = 'user_friends', blank=True, null=True)
bio = models.TextField(null=True, blank=True)
I'd want the user to be able to update email from UserChangeForm, something like this:
forms.py
class UserUpdateForm(UserChangeForm):
email = forms.EmailField()
def __init__(self, *args, **kwargs):
super(UserUpdateForm, self).__init__(*args, **kwargs)
self.fields.pop('username')
self.fields['email'].initial = self.instance.user.email
class Meta:
exclude = ('friends', 'username', 'password1', 'password2')
model = UserProfile
Except that one doesn't save the email.
I thought of trying to access request.post data from post_save signal so I could set user's new email there, but couldn't get that one working.
Any help is appreciated, thanks.

You need to override the form's save method as well:
class UserUpdateForm(UserChangeForm):
email = forms.EmailField()
class Meta:
exclude = ('friends', 'username', 'password1', 'password2')
model = UserProfile
def __init__(self, *args, **kwargs):
super(UserUpdateForm, self).__init__(*args, **kwargs)
self.fields.pop('username')
self.fields['email'].initial = self.instance.user.email
def save(self, commit=True):
self.instance.user.email = self.cleaned_data['email']
if commit:
self.instance.user.save()
super(UserUpdateForm, self).save(commit)

Related

django creating Profile with custom company fields try to conect with the owner

I have two forms (OwnerCreateForm, EmployeesCreateForm) and 3 models (Profile, Company and Owner). when the owner signs up, it creates the company and its own User object. after owner login, you can create employees.
Have the Owner connect to the profile and associate it with the company
I need to associate the owning company to the employees.
That each company manages its users, that they see the same thing
Here are the details I'm using:
MODELS
class Profile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def __str__(self):
return '{} Profile'.format(self.user)
class Owner(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
def __str__(self):
return '{} Owner'.format(self.profile)
class Tienda(models.Model):
dueƱo = models.ForeignKey(Owner, null=True, on_delete=models.CASCADE)
nombre_tienda = models.CharField(verbose_name='Nombre de la Tienda', max_length=120)
direccion = models.CharField(verbose_name='Su Direccion', max_length=160)
phone = models.CharField(max_length=11, null=True)
businessemail = models.EmailField(unique = True, verbose_name='Su email')
def __str__(self):
return self.nombre_tienda
class Employee(models.Model):
STATUS = (
('Admin', 'Admin'),
('Gerente', 'Gerente'),
('Validador', 'Validador')
)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
role = models.CharField(choices=STATUS, max_length=16)
tienda = models.ForeignKey(Tienda, null=True, on_delete=models.CASCADE)
def __str__(self):
texto = "{0} ({1}) {2}"
return texto.format(self.tienda, self.role, self.role)
FORMS
class TiendaForm(ModelForm):
class Meta:
model = Tienda
fields = ('nombre_tienda', 'direccion', 'businessemail')
class OwnerCreateForm(UserCreationForm):
class Meta:
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
model = User
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].label = 'Display Name'
self.fields['email'].label = "Email Address"
class EmployeesCreateForm(UserCreationForm):
is_admin = forms.BooleanField(required=False)
is_manager = forms.BooleanField(required=False)
is_systemAdmin = forms.BooleanField(required=False)
class Meta:
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
model = User
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].label = 'Display Name'
self.fields['email'].label = "Email Address"
VIEWS
def registroOwner(request):
if request.method == "POST":
form = OwnerCreateForm(request.POST)
tienda = TiendaForm(request.POST)
if form.is_valid() and tienda.is_valid():
tienda.save()
form.save()
messages.success(request, f'Tu cuenta ha sido creada!')
return redirect('login')
else:
form = OwnerCreateForm()
tienda = TiendaForm()
context = {
'title': 'Sign up Owner',
'form': form,
'tienda': tienda
}
return render(request, "accounts/signup.html", context)

DRF Non-field errors: Unable to log in with provided credentials

I am struggling to understand why my app suddenly wont allow me to log in a user after any changes are made to their profile. I have a nested User serializer with the profile serializer fields (onetoOne) using Djoser for the urls. When I try to update the user profile from the api endpoint it updates but throws an error that the avatar has no file associated to it. I thought that if I added "required=False" to to the ProfileSerializer it would negate this behaviour. Please help it is driving me crazy, I have googled and not found the answer why. I think it is in my create method within my UserSerializer class. It is also not saving the avatar if any of the other fields are changed within the profile object. Very strange. It was all working fine and for some reason now its not logging in users.
Here is the model:
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
name = models.CharField(max_length=100, blank=True, null=True)
occupation = models.CharField(max_length=100, blank=True, null=True)
residence = models.CharField(max_length=100, blank=True, null=True)
email = models.CharField(max_length=100, blank=True, null=True)
active_id = models.BooleanField(default=True)
avatar = models.ImageField(null=True, blank=True, upload_to ='uploads/profile_pics/',default='uploads/default.jpg')
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
img = Image.open(self.avatar.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.avatar.path)
Here is the serializers:
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = Profile
fields = ("__all__")
class ProfileStatusSerializer(serializers.ModelSerializer):
# user_profile = serializers.StringRelatedField(read_only=True)
class Meta:
model = ProfileStatus
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(required=False, allow_null=True, partial=True)
parser_class = (FileUploadParser,)
class Meta:
model = User
fields = ['username', 'profile','password', 'id']
extra_kwargs = {"password":{'write_only': True}}
def update(self, instance, validated_data):
if 'profile' in validated_data:
nested_serializer = self.fields['profile']
nested_instance = instance.profile
nested_data = validated_data.pop('profile')
nested_serializer.update(nested_instance, nested_data)
return super(UserSerializer, self).update(instance, validated_data)
def create(self, validated_data):
return User.objects.create_user(
username = validated_data['username'], # HERE
password = validated_data['password'])
Please halp.
Solved.
I needed to add validated_data fields to the create method.
def create(self, validated_data):
return User.objects.create_user(
validated_data['username'],None,validated_data['password'])

I can't update the formset

I cannot update using an inline form.
I thought it was possible before, but it wasn't.
I'm trying to solve it, but it doesn't work.
I added.
I will post any other necessary items.
"Id
This field is required.
user
A Profile with this User already exists.
"
I got an error.
#view
class UserEdit(generic.UpdateView):
model = User
form_class = forms.UserUpdateForm
template_name = 'accounts/accounts_edit.html'
success_url = reverse_lazy('person:myaccount')
def get_object(self):
return get_object_or_404(User, pk=self.request.user.user_id)
#model
class User(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
user_id = models.UUIDField(default=uuid_lib.uuid4,
primary_key=True, editable=False)
username = models.CharField(_('username'), unique=True, max_length=50,validators=[username_validator],error_messages={
'unique': _("A user with that username already exists."),
},)
class profile(models.Model):
image = models.ImageField(upload_to='profile/',default='profile/default.jpg')
first_name = models.CharField(_('first name'), max_length=30, blank=True,null=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True,null=True)
birthday = models.DateField(_('birthday',),null=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
#form
class ProfileUpdateForm(BaseModelForm):
class Meta:
model = profile
fields = ('first_name','last_name','birthday',)
ProfileFormSet = inlineformset_factory(User,profile,form=ProfileUpdateForm,extra=0)
class UserUpdateForm(mixins.ModelFormWithFormSetMixin,BaseModelForm):
formset_class = ProfileFormSet
class Meta:
model = User
fields = ('username','email',)
#mixin
class ModelFormWithFormSetMixin:
def __init__(self, *args, **kwargs):
super(ModelFormWithFormSetMixin, self).__init__(*args, **kwargs)
self.formset = self.formset_class(
instance=self.instance,
data=self.data if self.is_bound else None,
)
def is_valid(self):
return super(ModelFormWithFormSetMixin, self).is_valid() and self.formset.is_valid()
def save(self, commit=True):
saved_instance = super(ModelFormWithFormSetMixin, self).save(commit)
self.formset.save(commit)
return saved_instance

DJango REST Framework Read Only field

I have a field owner that is a ForeignKey to User model.
This field is required at the time of creation. But it can not be changed later on.
How to make fields Non-Editable? Is there any other way than creating multiple serializers?
Garage Model
class GarageDetails(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, )
name = models.CharField(_('Garage Name'), max_length=254, blank=False, null=False, unique=True)
price = models.IntegerField(_('Price'), blank=False)
available_from = models.TimeField(_('Available From'), default=datetime.time(6, 00), blank=False)
available_till = models.TimeField(_('Available till'), default=datetime.time(18, 00), blank=False)
description = models.TextField(_('Garage Description'), blank=True, null=True)
create_date = cmodels.UnixTimestampField(_('Date Added'), auto_now_add=True)
update_date = cmodels.UnixTimestampField(_('Date Added'), auto_created=True)
is_available = models.BooleanField(_('Available'), default=True)
Serializer
class UserFKSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('id', 'name', 'email', 'mobile')
class GarageSerializer(serializers.ModelSerializer):
owner = UserFKSerializer(many=False, read_only=True)
class Meta:
model = GarageDetails
fields = '__all__'
read_only_fields = ('id', 'owner', 'create_date', 'update_date')
Views
class GarageRegister(generics.ListCreateAPIView):
renderer_classes = (JSONRenderer, )
permission_classes = (IsAuthenticated, )
#csrf_exempt
def post(self, request, *args, **kwargs):
serialize = GarageSerializer(data=request.data)
if serialize.is_valid():
# Create Garage with owner & name
class GarageUpdate(generics.ListCreateAPIView):
renderer_classes = (JSONRenderer, )
permission_classes = (IsAuthenticated, )
#csrf_exempt
def post(self, request, *args, **kwargs):
serialize = GarageSerializer(data=request.data)
if serialize.is_valid():
# Update Garage but can't update create_date, id, owner & name
You could create a different model serializer for each use case (update, create):
specifying that field in read_only_fields in your model serializer:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('a', 'nother', 'field')
read_only_fields = ('owner',)
for django forms instead you set the disabled field:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
form.fields['owner'].widget.attrs['disabled'] = True
You can do this by overriding the "update" method as follows:
def update(self, instance, validated_data):
if 'owner' in validated_data:
del validated_data['owner']
return super().update(instance, validated_data)
This will silently ignore the owner field on updates. If you want to you may instead "raise ValidationError('owner may not be set on updates')" but if you do so you may want to read the model instance and only raise the error if it's actually a change to avoid false positives.
Also, if you're using the python2, the "super" call needs to be "super(GarageSerializer, self)" or some such.

Django REST overrride destroy method to make user inactive

I'm trying to first access the users table via the user foreign key present in userinformations models and later override the RetriveUpdateDestroy API view's destroy method to change the status of the user to inactive instead of deleting them. I can't seem to access the is-active field of the in built User database.
views.py
class UserUpdateApiView(RetrieveUpdateDestroyAPIView):
queryset = UserInformation.objects.all()
serializer_class = UserInformationUpdateSerializer
lookup_field = 'pk'
lookup_url_kwarg = 'id'
def destroy(self, request, *args, **kwargs):
try:
user = User.objects.get(pk=self.kwargs["id"])
deleteStatusVal = False
user.is_active = deleteStatusVal
user.save()
return Response(UserSerializer(user).data)
except:
return Response("Nope")
serializers.py
class UserSerializer(ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'}, write_only=True)
email = serializers.EmailField(validators=[required])
class Meta:
model = User
fields = ['username', 'email', 'password', 'is_active']
extra_kwargs = {'password': {'write_only': True},
'is_active': {'read_only': True}}
def validate(self, data):
email = data.get('email', None)
user = User.objects.filter(email=email).distinct()
if user.exists():
raise ValidationError("That email is already registered!")
return data
class UserInformationUpdateSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = UserInformation
fields = ['user', 'first_name', 'middle_name', 'last_name', 'phone', 'date_of_birth']
models.py
class UserInformation(BaseModel):
user = models.OneToOneField(User, related_name='user_id')
first_name = models.CharField(max_length=45)
middle_name = models.CharField(max_length=45, null=True)
last_name = models.CharField(max_length=45)
vendor = models.BooleanField(default=False)
phone = models.CharField(max_length=100, validators=[
RegexValidator(regex=r'^\+?8801?\d{9}$', message="Phone number must be entered in the format: '+8801*********'")
], blank=False, unique=True)
date_of_birth = models.DateField()
confirmation_token = models.CharField(max_length=45, null=True)
confirmation_exp = models.DateTimeField(null=True)
pw_reminder_token = models.CharField(max_length=45, null=True)
pw_reminder_exp = models.DateTimeField(null=True)
profile_pic = models.ImageField(blank=True, null=True, upload_to='profile_images/', default='Images/none/no_images.jpg')
cover_photo = models.ImageField(blank=True, null=True, upload_to='cover_images/', default='Images/none/no_images.jpg')
thumbnail_pic = models.ImageField(blank=True, null=True, upload_to='thumbnail_images/', default='Images/none/no_images.jpg')
phone_verified = models.BooleanField(default=False)
email_verified = models.BooleanField(default=False)
reward_points = models.IntegerField(null=False)
ref_code = models.CharField(null=True, max_length=10)
def __str__(self):
return self.user.username
def delete(self, *args, **kwargs):
self.user.delete()
super(UserInformation, self).delete(*args, **kwargs)
If you want to make User as in active while keeping the UserInformation object and Userobject un-deleted in database, you can do something like this:
def destroy(self, request, *args, **kwargs):
user = self.get_object().user
user.is_active = False
user.save()
return Response(UserInformationUpdateSerializer(self.get_object()).data)
You have 'is_active': {'read_only': True}}.
Also,
# this seems redundant
def delete(self, *args, **kwargs):
self.user.delete()
super(UserInformation, self).delete(*args, **kwargs)