Validate objects in many-to-many relationship in Django - python

I have two models Group and User. I have a ManyToManyField in Group referencing User. I also have an intermediate model GroupUser storing additional data (date for assigning and type of membership) to the many-to-many relationship.
I have a page /groups/group-<group_id>/create-user/ at which I add users to the group. My problem is that if a user is created as type 1, he cannot be assigned to other groups. How can I validate that the user is not assigned to other groups if a relationship already exists with the specific user and type 1?
I'm using a CreateView
class GroupUserCreateView(CreateView):
model = GroupUser
fields = ['user', 'type']
template_name = "group_user_create_form.html"
def dispatch(self, request, *args, **kwargs):
self.group = get_object_or_404(Group, id=self.kwargs['group_id'])
return super(GroupUserCreateView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.group = self.group
return super(GroupUserCreateView, self).form_valid(form)
def get_success_url(self):
return reverse('group_user_list', kwargs={'group_id': self.group.id})

Don't know much about CreateView, but my guess, overwrite get_form and adjust the queryset on the on the user field.
def get_form(self, form_class):
create_form = super(GroupUserCreateView, self).get_form(form_class)
create_form.fields['user'].queryset = User.objects.exclude(groupuser__type=1)
return create_form

Related

how can i check django user groups in forms

I have a form defined from a model class. Depending from the user group of the logged in user, some fields should be remove if logged in user is not belong from certain group.i have a producer group, if logged in user is not belong form producer group then i want to remove the time_pool field from the forms. my code is showing this error 'NoneType' object has no attribute 'groups' . how can i solve this issue?
my code is working i want to know is that a correct way if i initialized the user like this ?
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = [
'title',
'content',
'time_pool',
]
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user', None)
if not self.user.groups.filter(name__iexact='producer').exists():
del self.fields['time_pool']
views.py
class NewsCreateView(LoginRequiredMixin, CreateView, SuccessMessageMixin):
form_class = ArticleForm
template_name = 'news/news_create.html'
success_url = '/'
success_message = "%(title)s was created successfully"
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(NewsCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

Django: Create object with ForeignKey from url

I am working on a Django project with two models linked by a ForeignKey. The parent model, Composition, is linked to the child model, NoteObject, by the id of Composition.
in models.py
class Composition(models.Model):
id = models.AutoField(primary_key=True)
...
class NoteObject(models.Model):
id = models.AutoField(primary_key=True)
composition = models.ForeignKey(Composition, on_delete=models.CASCADE)
...
Once a composition is created, the user needs to be able to create NoteObjects that belong to that composition. The notes are created with the following method:
in views.py
class NoteCreateView(CreateView):
model = NoteObject
template_name = 'entry.html'
fields = ['duration', 'pitch', 'accidental', 'octave']
success_url = reverse_lazy('compositions')
def get_context_data(self, **kwargs):
kwargs['notes'] = NoteObject.objects.filter(
composition=self.kwargs['composition'])
return super(NoteCreateView, self).get_context_data(**kwargs)
The get_context_data method is there to display only the notes for the current composition. The current composition comes from the id of the composition that is part of the url where <composition> is the id of the composition.
in urls.py
path('entry/<composition>/', views.NoteCreateView.as_view(), name='entry')
When I save a NoteObject, what do I need to do in order to set the value of the ForeignKey to be the value within <composition>?
in models.py
def save(self, *args, **kwargs):
composition_id = ????????
self.composition_id = composition_id
super(NoteObject, self).save(*args, **kwargs)
How do I get the value of kwarg in the CreateView to be the ForeignKey when the object is created?
I think you can do it by over-riding form_valid method as mentioned in docs here.
class NoteCreateView(CreateView):
model = NoteObject
template_name = 'entry.html'
fields = ['duration', 'pitch', 'accidental', 'octave']
success_url = reverse_lazy('compositions')
def form_valid(self, form):
form.instance.composition = self.kwargs['composition']
return super(NoteCreateView, self).form_valid(form)
In order to make this work, you need to override the dispatch function as well.
def dispatch(self, request, *args, **kwargs):
self.composition = Composition.objects.values_list(
'id').filter(pk=kwargs['composition'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.composition_id = self.composition
return super().form_valid(form)
I used this solution and modified it slightly: CreateView Set ForeignKey from Url Parameter.

Set values for form field by query using filter

I want to set the item in dropdown using the query in the form. I want to add employee and the select company which using filter Is_Del= 0. I do not know how to set values for the drop down and where to write this query.
I tried to put in Forms.py, but it is not working.
This is form.py
class EmployeeCreateForm(forms.ModelForm):
class Meta:
model = Employee
fields = ('Emp_Name','Emp_company','Emp_Dept','Emp_Join_Date', 'Emp_End_Date')
def clean(self):
cleaned_data = super(EmployeeCreateForm, self).clean()
Emp_Name = cleaned_data.get('Emp_Name')
Emp_company = cleaned_data.get('Emp_company')
Emp_Dept = cleaned_data.get('Emp_Dept')
Emp_Join_Date = cleaned_data.get('Emp_Join_Date')
Emp_End_Date = cleaned_data.get('Emp_End_Date')
return cleaned_data
def __init__(self, *args, **kwargs):
super(EmployeeCreateForm,self).__init__(*args, **kwargs)
self.fields['Emp_company'].queryset = Company.objects.filter(Is_Del=0)
and below is my view.py
class EmployeeCraeteView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
model=Employee
form = EmployeeCreateForm
success_message = " Employee Craeted successfully!"
success_url="../../company/all-companies"
template_name = 'employee_form.html'
fields =[
'Emp_Name','Emp_company','Emp_Dept','Emp_Join_Date',
'Emp_End_Date'
]
companies= Company.objects.filter(Is_Del=0)
def form_valid(self,form):
form.instance.Emp_Crt_By = self.request.user
if form.cleaned_data['Emp_Join_Date'] >= form.cleaned_data['Emp_End_Date']:
form.add_error('Emp_End_Date', 'Joining date should be less than Ending date')
return self.form_invalid(form)
return super(EmployeeCraeteView, self).form_valid(form)
I want to show only this companies in the form which are filtered by Is_Del =0
Your EmployeeCreateView is wrong:
Remove the attributes form, fields and companies
Add form_class = EmployeeCreateForm.
The reason is that form doesn't do anything in a CreateView (see here). To use a custom form class, you need to pass it to form_class.
Your CreateView was dynamically creating the form using a modelform_factory with the fields you defined (if you hadn't added those you'd have seen your mistake immediately) and so your EmployeeCreateForm is never instantiated.

Change serializers on per-object basis within one ViewSet?

I'm working on a project with some social features and need to make it so that a User can see all details of his profile, but only public parts of others' profiles.
Is there a way to do this within one ViewSet?
Here's a sample of my model:
class Profile(TimestampedModel):
user = models.OneToOneField(User)
nickname = models.CharField(max_length=255)
sex = models.CharField(
max_length=1, default='M',
choices=(('M', 'Male'), ('F', 'Female')))
birthday = models.DateField(blank=True, null=True)
For this model, I'd like the birthday, for example, to stay private.
In the actual model there's about a dozen such fields.
My serializers:
class FullProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
class BasicProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = read_only_fields = ('nickname', 'sex', 'birthday')
A custom permission I wrote:
class ProfilePermission(permissions.BasePermission):
"""
Handles permissions for users. The basic rules are
- owner and staff may do anything
- others can only GET
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
else:
return request.user == obj.user or request.user.is_staff
And my viewset:
class RUViewSet(
mixins.RetrieveModelMixin, mixins.UpdateModelMixin,
mixins.ListModelMixin, viewsets.GenericViewSet):
"""ViewSet with update/retrieve powers."""
class ProfileViewSet(RUViewSet):
model = Profile
queryset = Profile.objects.all()
permission_classes = (IsAuthenticated, ProfilePermission)
def get_serializer_class(self):
user = self.request.user
if user.is_staff:
return FullProfileSerializer
return BasicProfileSerializer
What I'd like is for request.user's own profile in the queryset to be serialized using FullProfileSerializer, but the rest using BasicProfileSerializer.
Is this at all possible using DRF's API?
We can override the retrieve() and list methods in our ProfileViewSet to return different serialized data depending on the user being viewed.
In the list method, we serialize all the user instances excluding the current user with the serializer returned from get_serializer_class() method. Then we serialize the current user profile information using the FullProfileSerializer explicitly and add this serialized data to the data returned before.
In the retrieve method, we set a accessed_profile attribute on the view to know about the user the view is displaying. Then, we will use this attribute to decide the serializer in the get_serializer_class() method.
class ProfileViewSet(RUViewSet):
model = Profile
queryset = Profile.objects.all()
permission_classes = (IsAuthenticated, ProfilePermission)
def list(self, request, *args, **kwargs):
instance = self.filter_queryset(self.get_queryset()).exclude(user=self.request.user)
page = self.paginate_queryset(instance)
if page is not None:
serializer = self.get_pagination_serializer(page)
else:
serializer = self.get_serializer(instance, many=True)
other_profiles_data = serializer.data # serialized profiles data for users other than current user
current_user_profile = <get_the_current_user_profile_object>
current_user_profile_data = FullProfileSerializer(current_user_profile).data
all_profiles_data = other_profiles_data.append(current_user_profile_data)
return Response(all_profiles_data)
def retrieve(self, request, *args, **kwargs):
self.accessed_profile = self.get_object() # set this as on attribute on the view
serializer = self.get_serializer(self.accessed_profile)
return Response(serializer.data)
def get_serializer_class(self):
current_user = self.request.user
if current_user.is_staff or (self.action=='retrieve' and self.accessed_profile.user==current_user):
return FullProfileSerializer
return BasicProfileSerializer
I managed to hack together the solution that provides the wanted behaviour for the detail view:
class ProfileViewSet(RUViewSet):
model = Profile
queryset = Profile.objects.all()
permission_classes = (IsAuthenticated, ProfilePermission)
def get_serializer_class(self):
user = self.request.user
if user.is_staff:
return FullProfileSerializer
return BasicProfileSerializer
def get_serializer(self, instance=None, *args, **kwargs):
if hasattr(instance, 'user'):
user = self.request.user
if instance.user == user or user.is_staff:
kwargs['instance'] = instance
kwargs['context'] = self.get_serializer_context()
return FullProfileSerializer(*args, **kwargs)
return super(ProfileViewSet, self).get_serializer(
instance, *args, **kwargs)
This doesn't work for the list view, however, as that one provides the get_serializer method with a Django Queryset object in place of an actual instance.
I'd still like to see this behaviour in a list view, i.e. when serializing many objects, so if anyone knows a more elegant way to do this that also covers the list view I'd much appreciate your answer.

How To Exclude A Value In A ModelMultipleChoiceField?

I do not want the logged in user to show up on this ModelMultipleChoiceField in order to restrict themselves from creating a following relationship with themselves? So how do I exclude the logged in user from the queryset, probably an easy fix but I'm new to Django and it has eluded me for a few hours now.
forms.py
class Add_Profile(forms.ModelForm):
def __init__(self,*args, **kwargs): # initializing your form in other words loading it
super(Add_Profile, self).__init__(*args, **kwargs)
user_id = kwargs.pop('user_id') # taking user_id out of the querylist
self.fields['follows'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=UserProfile.objects.filter(~Q(id=user_id)))
class Meta:
model = UserProfile
fields = (
'bio',
'follows',
'theme',
'profile_picture',
)
Views.py
#login_required
def edit_profile(request, user_id):
userprofile = UserProfile.objects.get(pk=user_id)
if request.method == 'POST':
edit_profile = Add_Profile(request.POST, request.FILES, instance=userprofile, user_id=request.user.id)
if edit_profile.is_valid():
edit_profile.save()
return redirect('/home/user/{0}/'.format(request.user.username))
else:
print edit_profile.errors
else:
edit_profile = Add_Profile(instance=userprofile, user_id=request.user.id)
return render (request, 'edit.html', {'form': edit_profile,})
Error: init() got an unexpected keyword argument 'user_id'
You can definitely do it using forms.Form instead of forms.ModelForm with something along the lines of this example in the docs:
from django import forms
from django.contrib.auth import get_user_model
class Add_Profile(forms.Form):
follows = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, user=None, *args, **kwargs):
super(Add_Profile, self).__init__(*args, **kwargs)
if user is not None:
self.fields['follows'].queryset = get_user_model().objects.exclude(pk=user.pk)
else:
self.fields['follows'].queryset = get_user_model.objects.all()
Just pass in the user you wish to exclude when you instantiate the form:
form = Add_Profile() # all users will be present in the dropdown
some_guy = User.objects.get(pk=4)
form = Add_Profile(user=some_guy) # all users except some_guy will be present
Define an __init__ method for the form class. Pass the logged in userid to the form while initializing it, this will work with a model form.
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id')
super(Add_Profile, self).__init__(*args, **kwargs)
self.fields['follows'] = forms.ModelMultipleChoiceField(queryset=UserProfile.objects.filter(~Q(user_id=user_id)))
While initializing your form, you can pass user_id
address_form = Add_Profile(request.POST, user_id=request.user.id)

Categories

Resources