(Django) Limited ForeignKey choices by Current User in UpdateView - python

I recently was able to figure out how to do this in the CreateView, but the same is not working for the UpdateView (Here's the original post on how to do it in the CreateView: (Django) Limited ForeignKey choices by Current User)
Essentially, I need it to display only the Universes created by the currently logged in user, but by default, it displays all universes.
When I try to set a form_class and have it mimic the solution for CreatView, it spits out an improperly configured error.
models.py:
class Universe(models.Model):
user = models.ForeignKey(User,related_name='universe',on_delete=models.CASCADE)
name = models.CharField(max_length=100, unique=True)
description = models.TextField(max_length=2000,blank=True,default="")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('universe:singleuniverse',kwargs={'pk': self.pk})
class Meta:
ordering = ['name']
unique_together = ['user','name']
class Character(models.Model):
user = models.ForeignKey(User,related_name='characters',on_delete=models.CASCADE)
universe = models.ForeignKey("story_universe.Universe", on_delete=models.CASCADE)
name = models.CharField(max_length=255,unique=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('character_developer:singlecharacter',kwargs={'pk': self.pk})
class Meta():
ordering = ['name']
unique_together=['user','name']
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
model = Character
fields = ('universe','name')
template_name = 'character_developer/character_update_form.html'
UPDATE
The error was:
Error: ImproperlyConfigured at /characters/update/3/
UpdateCharacter is missing a QuerySet. Define UpdateCharacter.model, UpdateCharacter.queryset, or override UpdateCharacter.get_queryset().
and here's what the code looked like to get the error:
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
template_name = 'character_developer/character_update_form.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
forms.py
class UpdateForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
user = kwargs.pop('user')
super(UpdateForm,self).__init__(*args,**kwargs)
self.fields['universe'].queryset = Universe.objects.filter(user=user)
class Meta:
model = Character
fields = ('universe','name')

I believe you need the following in your views.py (almost an exact extension of your CreateCharacter):
class UpdateCharacter(LoginRequiredMixin, generic.UpdateView):
model = Character
template_name ='character_developer/character_create.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self,form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save
return super().form_valid(form)
I would caveat the above - make sure that the user instance in the request object is that of the currently logged in user, and has sufficient permission to update their own user - and can't somehow make a request on their behalf.

Related

How to allow UpdateView only to a user that owns that model - and that model is already connected with a foreign key

I want the Topping model to only be accessible to a current user, and not to other users who could access that page by copying the URL.
models.py
class Pizza(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
def get_absolute_url(self):
return "/pizzas"
class Topping(models.Model):
pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("pizza", kwargs={"pizza_id": self.pizza.pk})
views.py
class UpdateTopping(LoginRequiredMixin, UpdateView):
model = Topping
form_class = UpdateToppingForm
template_name = "pizzas/update_topping.html"
Something along these lines (this has worked on the primary model):
class UpdatePizza(LoginRequiredMixin, UpdateView):
model = Pizza
form_class = UpdatePizzaForm
template_name = "pizzas/update_pizza.html"
def get_queryset(self):
base_qs = super(UpdatePizza, self).get_queryset()
return base_qs.filter(owner=self.request.user)
You can do something like this:
from django.core.exceptions import PermissionDenied
class UpdateTopping(LoginRequiredMixin, UpdateView):
model = Topping
form_class = UpdateToppingForm
template_name = "pizzas/update_topping.html"
def form_valid(self, form):
if self.request.user != form.cleaned_data['pizza'].user:
raise PermissionDenied(f"You are not allowed to edit this topping.")

Private messaging system Django

I've been trying to set up a basic private messaging system in Django using the generic CreateView.
I am currently having trouble with the "Receiver"/"To" field in my form. I tried to make it so it was a drop down field with the options being followers of the logged-in user.
Currently, the field is populating with the correct usernames (in this case, "testuser1") but it is throwing an error saying this field needs to be populated with an instance of the User object.
ValueError: Cannot assign "'testuser1'": "Message.reciever" must be a "User" instance.
Is there a way to have the form pass in the object of the username that is selected?
Model:
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reciever")
subject = models.CharField(max_length=128, default="-")
content = models.TextField()
send_date = models.DateTimeField(default=timezone.now)
User Relationships Model:
class UserRelationships(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
created = models.DateTimeField(auto_now_add=True)
UPDATED Form:
class MessageCreateForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender', 'reciever', 'subject', 'content']
widgets = {'sender': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
follower_objects = kwargs.pop('follower_objects')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'] = RecieverModelChoiceField(queryset=User.objects.filter(username__in=follower_objects))
View:
class MessageCreateView(LoginRequiredMixin, CreateView):
model = Message
template_name = 'message/compose.html'
form_class = MessageCreateForm
def get_initial(self):
initial = super().get_initial()
initial['sender'] = self.request.user
return initial
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
user = self.request.user
followers = user.followers.values_list('user_id', flat=True)
follower_objects = []
kwargs['user'] = self.request.user
kwargs['follower_objects'] = follower_objects
for id in followers:
follower = User.objects.get(id=id)
follower_objects.append(follower)
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
You have to use forms.ModelChoiceField instead of forms.ChoiceField
ForeignKey (model) > ModelChoiceField (form) - Default widget: Select
ModelChoiceField has attribute queryset.
You can filter field reciever.queryset directly in MessageCreateForm.__init__ method.
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'].queryset = user.followers
UPDATE:
You can set a custom ModelChoiceField that will return any label you want (more info).
from django.forms import ModelChoiceField
class RecieverModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.username
or
def __init__(self, *args, **kwargs):
....
self.fields['reciever'].label_from_instance = lambda obj: "%s" % obj.username

How to access a logged in user from a class based view?

How do I access a current logged in user from a class-based view?
In a function-based view we can pass a request parameter but I can't pass a request parameter from a class view.
I have seen ways to do it in the internet but I can't understand it.
my models.py file
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return (self.name)
def get_absolute_url(self):
return reverse("home")
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(max_length=3500)
category = models.CharField(max_length=255, default="uncategorized")
views.py
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
Thank you.
You can use self.request.user inside methods of class-based views; as an example:
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
def get_context_data(self):
current_loggedin_user = self.request.user
# ...
Edit (just print the username):
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
def __init__(self, *args, **kwargs):
print(self.request.user, self.request.user.username) # print user & username
return super().__init__(*args, **kwargs)

Attaching the logged-in User to object after object creation

I am trying to have a logged in User fill out a form to create a Group. On Group creation, I need the User to automatically be added to the Group.
For this problem, we are working with two models - User and Group.
User is the default model provided by Django.
Group is defined like so:
class Group(models.Model):
name = models.CharField(max_length=255, unique=True)
admins = models.ManyToManyField(User, default=1, related_name='user_username')
all_users = models.ManyToManyField(User, default=1)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('home')
def get_admins(self):
return ", ".join([u.username for u in self.admins.all()])
def add_admin(self, user):
self.admins.add(user)
def get_all_users(self):
return ", ".join([u.username for u in self.all_users.all()])
def add_user(self, user):
self.all_users.add(user)
self.save()
def is_admin(self, user):
if user in self.admins.all():
return True
else:
return False
And the view I'm trying to refactor is:
#login_required
def user_generated_group(request):
if request.method == 'POST':
form = GroupForm(request.POST)
user = request.user
if form.is_valid():
group = Group.objects.create(name=form.cleaned_data['name'])
group.add_admin(user)
group.add_user(user)
group.save()
return HttpResponseRedirect(reverse('home'))
else:
form = GroupForm()
context = {
'form': form,
'type': 'group',
'sidebar': Sidebar(request),
}
return render(request, 'form.html', context)
Here is the GroupForm:
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = '__all__'
exclude = ['all_users', 'admins', ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class': 'input'})
The goal is to utilize Django's built-in CreateView. The refactored view so far looks like:
class CreateGroup(LoginRequiredMixin, CreateView):
model = Group
form_class = GroupForm
template_name = 'form.html'
I have yet to implement the add_user and add_admin logic to this view. The hope is that I can manage these methods elsewhere.
I'm unsure of where to go from here. Should this logic be handled by the User or the Group?
If by the Group, should I be using a Manager?
If by the User, should I create a custom User model?
You can override the form_valid [Django-doc] method, to add the self.request.user to the group, like:
from django.http import HttpResponseRedirect
class CreateGroup(LoginRequiredMixin, CreateView):
model = Group
form_class = GroupForm
template_name = 'form.html'
def form_valid(self, form):
self.object = group = form.save()
group.all_users.add(self.request.user)
return HttpResponseRedirect(self.get_success_url())

Django inline forms cannot query, must be model instance

I'm trying to make inline forms with class based views, i followed the instrunctions from here. The only change i've made was to give self.request.user instead of self.object to instance:
models.py
class Client(models.Model):
user = models.OneToOneField(CustomUser)
translate = models.BooleanField(default=False)
class ClientData(models.Model):
client = models.ForeignKey(Client)
language = models.ForeignKey(Language)
forms.py
class ClientForm(ModelForm):
class Meta:
model = Client
fields = '__all__'
exclude = ['user', ]
class ClientDataForm(ModelForm):
class Meta:
model = ClientData
fields = '__all__'
exclude = ['client', ]
ClientFormSet = inlineformset_factory(Client, ClientData, form=ClientDataForm, extra=1)
views.py
class ClientView(LoginRequiredMixin, UpdateView):
model = Client
fields = '__all__'
success_url = reverse_lazy('core:index')
class ClientDataView(LoginRequiredMixin, UpdateView):
template_name = 'core/client_data.html'
model = ClientData
form_class = ClientDataForm
success_url = reverse_lazy('core:index')
def get_object(self, queryset=None):
profile = get_object_or_404(ClientData, client__user=self.request.user)
return profile
def get_context_data(self, **kwargs):
context = super(ClientDataView, self).get_context_data(**kwargs)
if self.request.POST:
context['client_data'] = ClientFormSet(self.request.POST, instance=self.get_object())
else:
context['client_data'] = ClientFormSet(instance=self.get_object())
return context
def form_valid(self, form):
context = self.get_context_data()
client_data = context['client_data']
with transaction.atomic():
self.object = form.save()
if client_data.is_valid():
client_data.instance = self.object
return super(ClientDataView, self).form_valid(form)
Whenever i try to enter the page i get:
ValueError: Cannot query "asd#gmail.com": Must be "Client" instance.
[13/Dec/2017 15:48:36] "GET /client-data/ HTTP/1.1" 500 143759
for this line:
context['client_data'] = ClientFormSet(instance=self.get_object())
Your get_object is returning a ClientData instance.
def get_object(self, queryset=None):
profile = get_object_or_404(ClientData, client__user=self.request.user)
return profile
However, as the error suggests, the instance should be a Client instance, for example:
def get_object(self, queryset=None):
profile = get_object_or_404(Client, user=self.request.user)
return profile
Or you can simply follow the one-to-one relation backwards:
def get_object(self, queryset=None):
return self.request.user.client

Categories