Overriding django form in Forms.modelAdmin - python

I have a problem when I want to override a field in my form
The class looks like below
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
fields = ['password', 'services', 'project', 'email', 'name', 'first_name', 'role']
I want to modify the field 'Services' with another value.
For this I used the get_form function like this :
class UserAdmin(admin.ModelAdmin):
exclude = ('uuid',)
search_fields = ('email', "project")
list_display = ("email", "project", "nb_connexion")
form = UserCreationForm
def get_form(self, request, obj=None, **kwargs):
if obj != None:
print(obj.name)
print(request)
form = super(UserAdmin, self).get_form(request, obj=obj, **kwargs)
form.base_fields['services'].initial = Service.objects.filter(projects=obj.project)
return form
But I still get the same results for all the services while I am only interested in getting the services of one project.Any help would be appreciated.
Thanks

You are currently trying to set initial, which is the initial value selected. If you want to limit the choices, you want to override the queryset instead.
You can alter the form's fields in the __init__ method:
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
fields = ['password', 'services', 'project', 'email', 'name', 'first_name', 'role']
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['services'].queryset = Service.objects.filter(projects=self.instance.project)
Your UserAdmin class already uses form = UserCreationForm, so all you have to do is remove the get_form method.
class UserAdmin(admin.ModelAdmin):
...
form = UserCreationForm
...

Related

Filter dropdown in django forms

In forms, I am trying to filter marketplace drop down field that belong to the logged in user based on its group. Its listing all the dropdown field items. I tried below but I think something is wrong with the filter part.
class InfringementForm(ModelForm):
def __init__(self, user, *args, **kwargs):
super(InfringementForm,self).__init__(*args, **kwargs)
self.fields['marketplace'].queryset =
Marketplace.objects.filter(groups__user=self.user)
class Meta:
model = Infringement
class Meta:
ordering = ['-updated', '-created']
def __str__(self):
return self.name
fields = ['name', 'link', 'infringer', 'player', 'remove', 'status',
'screenshot','marketplace']
models.py
class Marketplace (models.Model):
name = models.CharField(max_length=100)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
groups = models.ForeignKey(Group, on_delete=models.CASCADE,default=1)
Try this inside __init__() method:
def __init__(self, user, *args, **kwargs):
self.user = user
super(InfringementForm,self).__init__(*args, **kwargs)
self.fields['marketplace'].queryset = Marketplace.objects.filter(groups__user=self.user)
final answer is adding self.user = user in the forms and adding user in the view.
forms.py
class InfringementForm(ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(InfringementForm,self).__init__(*args, **kwargs)
self.fields['marketplace'].queryset =
Marketplace.objects.filter(groups__user=self.user)
class Meta:
model = Infringement
fields = ['name', 'link', 'infringer', 'player', 'remove', 'status',
'screenshot', 'marketplace']
views.py
#login_required(login_url='login')
def createInfringement(request):
user=request.user
form = InfringementForm(user=request.user)
if request.method == 'POST':
form = InfringementForm(user, request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('home') context ={'form': form} return render (request, 'base/infringement_form.html', context)
Try this solution...
Basically groups is foreign key in Marketplace model it returns a full object of the Group model.
you tried to filter with the full object it's not possible in a query, so you can filter using id of the user instance
Code becomes like this
class InfringementForm(ModelForm):
def __init__(self, user, *args, **kwargs):
super(InfringementForm,self).__init__(*args, **kwargs)
self.fields['marketplace'].queryset =
Marketplace.objects.filter(groups__user__id=self.user.id)
class Meta:
model = Infringement

Implementing case-insensitive username and email with class base view in Django 3

I'm trying to implement a registration mechanism that should have case insensitive query on username and email with class base view in a Django project.
I tried many methods but none of them worked. Anybody Has A Solution??
views.py:
class RegisterView(FormView):
template_name = 'users/user_register.html'
form_class = UserRegistrationForm
redirect_authenticated_user = True
success_url = reverse_lazy('index')
def form_valid(self, form):
user = form.save()
if user is not None:
login(self.request, user)
return super(RegisterView, self).form_valid(form)
def get(self, *args, **kwargs):
if self.request.user.is_authenticated:
return redirect('index')
return super(RegisterView, self).get(*args, **kwargs)
forms.py:
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
You can use the clean_<field_name> method inside your form.
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
def clean_username(self):
username = self.cleaned_data.get('username')
If User.objects.filter(username__iexact=username).exists():
raise forms.ValidationError('Username Already Exists')
return username
# For username, you have to create your own usermanager class and point it in your user model class. For email id, django uses the normalize_email function in BaseUserManager which always change the email ids in lowercase. You can customize that function in BaseUserManager, If you want.
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
class UserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
class User(AbstractBaseUser):
objects = UserManager()
# other changes

How to update with SerializerMethodField in Django Rest Framework

I have a field in my ModelSerializer which I've set as SerializerMethodField to modify the get behaviour for the field. I could update the data before, now I can't. How can I solve this?
Initially, without using SerializerMethodField, I got data like this:
{
...
"members": [2,3],
...
}
but I added SerializerMethodField to modify the data, then update stopped working.
models.py
# Create your models here.
class Company(models.Model):
members = ArrayField(models.IntegerField(blank=True), blank=True)
...
serializers.py
class AccountSerializer(serializers.ModelSerializer):
user=serializers.StringRelatedField(read_only=False)
class Meta:
model=Account
fields='__all__'
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
members = serializers.SerializerMethodField()
class Meta:
model = Company
fields = '__all__' #('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members')
def get_members(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...
You need to use different serializers for update and create. This serializer works for get only.
Or, you can create a custom field. Django Rest Framework How to update SerializerMethodField
Or, there can be other simpler hooks. If 'create' and 'update' worked as you wanted before modifiying members, then you can do as follow to get everything to default for create and update requests.
Instead of using SerializerMethodField, override serializer representation.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
class Meta:
model = Company
fields = ('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members', 'members_data')
def to_representation(self, obj)
ret = super().to_representation(obj)
ret["members"] = AccountSerializer(accounts, many=True).data
return ret
Override the __init__ method .
.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
if self.context['request'].method in ['GET']:
self.fields['members'] = SerializerMethodField()
except KeyError:
pass
class Meta:
model = Company
fields = '__all__' #('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members')
def get_members(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...
Or, you can create different field for getting members.
class CompanySerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=False)
members_data = SerializerMethodField()
class Meta:
model = Company
fields = ('id', 'name', 'description', 'date_created', 'user', 'status', 'theme', 'members', 'members_data')
def get_members_data(self, obj):
accounts = Account.objects.filter(id__in=obj.members)
return AccountSerializer(accounts, many=True).data
...

Django, REST framework - How to ensure absolute url will be returned for image?

I need viewset to return always absolute url for avatar / avatar_thumbnail image fields in response, but I'm getting relative paths. I have two such cases: 1st) is in image field in nested serializer, 2nd) is in viewset where I want to use two serializers in retrieve method.
models.py
class CustomUser(AbstractUser):
avatar = ProcessedImageField(upload_to='users/',format='JPEG',options={'quality': 60})
avatar_thumbnail = ImageSpecField(source='avatar',processors=[ResizeToFill(50, 50)],format='JPEG')
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
urls.py
if settings.DEBUG:
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)here
forum/api/serializers.py (nested serializer)
class UserSerializer(serializers.ModelSerializer):
avatar_thumbnail = serializers.ImageField(read_only=True)
class Meta:
model = CustomUser
fields = ['id', 'username', 'avatar_thumbnail']
class ThreadSerializer(serializers.ModelSerializer):
class Meta:
model = Thread
fields = ['id', 'title', 'subject', 'user']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['user'] = UserSerializer(instance.user).data
return representation
forum/api/views.py
class ThreadViewSet(viewsets.ModelViewSet):
serializer_class = ThreadSerializer
queryset = Thread.objects.all()
For UserViewSet I have also similar problem with urls for "avatar". I need absolute urls. I get relative url, but only when I overwrite retrieve method in viewset. (I overwrite it to use different serializer "UserPrivateSerializer" for user that is owner of the profile) .For list I always get absolute url.
users/api/serializers.py
class UserPublicSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id', 'username', 'avatar']
class UserPrivateSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
class Meta:
model = CustomUser
fields = ['id', 'username', 'email', 'avatar']
users/api/views.py
(Here the problem is with additional serializer, that I want to use in retrieve method)
class UserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.exclude(username='user_deleted')
serializer_class = UserPublicSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def retrieve(self, request, *args, **kwargs):
"""Custom retrieve method use UserPrivateSerializer if the user object is requested by it's owner.
"""
super().retrieve(request, *args, **kwargs)
instance = self.get_object()
if request.user == instance:
serializer = UserPrivateSerializer(instance)
else:
serializer = UserPublicSerializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
I have found out that the problem is solved when I add context to serializers.
forum/api/serializers.py
class ThreadSerializer(serializers.ModelSerializer):
class Meta:
model = Thread
fields = ['id', 'title', 'subject', 'user']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['user'] = UserSerializer(instance.user, context=self.context).data
return representation
users/api/views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.exclude(username='user_deleted')
serializer_class = UserPublicSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def retrieve(self, request, *args, **kwargs):
super().retrieve(request, *args, **kwargs)
instance = self.get_object()
if request.user == instance:
serializer = UserPrivateSerializer(instance, context={'request': request})
else:
serializer = UserPublicSerializer(instance, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
I have found my answer here: Django REST Framework and FileField absolute url
"The request must available to the serializer, so it can build the full absolute URL for you. One way is to explicitly pass it in when the serializer is created"

How to return email verified along with user endpoint?

I have implemented DRF and don't want to force email validation, but still use it(remind them on screen). I would like to just return account_emailaddress.verified along with the user endpoint. What is the best way to achieve this? I tried taking hints from this post, but didn't have success.
The account_emailaddress comes from the django-allauth app I have implemented
https://github.com/pennersr/django-allauth/blob/master/allauth/account/models.py
Serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar')
View:
class UserView(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
try:
user = self.queryset.get(pk=kwargs["user"])
return Response(UserSerializer(user).data)
except CustomUser.DoesNotExist:
return Response(
data={
"message": "User with id: {} does not exist".format(kwargs["user"])
},
status=status.HTTP_404_NOT_FOUND
)
def put(self, request, *args, **kwargs):
try:
user = self.queryset.get(pk=kwargs["user"])
serializer = UserSerializer()
user = serializer.update(user, request.data)
return Response(UserSerializer(user).data)
except CustomUser.DoesNotExist:
return Response(
data={
"message": "User with id: {} does not exist".format(kwargs["user"])
},
status=status.HTTP_404_NOT_FOUND
)
Edit: Thanks to schillingt answer I was able to modify the serializer to work as needed
from allauth.account.models import EmailAddress
class UserSerializer(serializers.ModelSerializer):
verified_email = serializers.SerializerMethodField()
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar', 'verified_email')
def get_verified_email(self, obj):
try:
email_address = EmailAddress.objects.get(user_id=obj.id)
return email_address.verified
except EmailAddress.DoesNotExist:
return None
The way I've done it in the past if I just want the field on a different serializer is to use the SerializerMethod class. I guessed at the class for account_emailaddress.
class UserSerializer(serializers.ModelSerializer):
verified_email = serializers.SerializerMethod()
class Meta:
model = models.CustomUser
fields = (..., 'verified_email')
def _verified_email(self, obj):
try:
return obj.account_emailaddress.verified
except EmailAddress.DoesNotExist:
return None
Then on the viewset, you should include account_emailaddress in the select_related on the queryset property so that it doesn't make an additional query per CustomUser.
You can use a source mapping on a serializer field to include fields from a related model, like so (assuming verified is a boolean field);
class UserSerializer(serializers.ModelSerializer):
verified = serializers.BooleanField(source='account_emailaddress.verified', read_only=True)
class Meta:
model = models.CustomUser
fields = ('id', 'first_name', 'last_name', 'email', 'phone_number', 'avatar', 'verified ')
Please note that, this also assumes that account_emailaddress is a field on CustomUser model or can be accessed like custom_user.account_emailaddress through a OneToOne field.

Categories