Cross queries in django - django

I have two models as below
class Watched(Stamping):
user = models.ForeignKey("User", null=True, blank=True, on_delete=models.CASCADE,
default=None)
count = models.PositiveIntegerField()
class Link(Stamping):
...
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
url = models.CharField(max_length=256, default=None)
watched = models.ForeignKey(Watched, null=True, blank=True, on_delete=models.CASCADE, default=None)
...
My forms.py
class SimpleLink(forms.Form):
url = forms.URLField(max_length=256)
A user can create a Link object and when some conditions are met, the object will be added to Watched. The Watched model contains objects created by different users.
Now I want to filter the Watched class and grab only the objects created by the requesting user in the Link model but I don't know how I can achieve that. Any help will be appreciated.
A sample of what I want to achieve is...
Watched.objects.filter(Link.objects.filter(user=request.user). I know my sample is crazy. But from the outside query, I want to grab the Link objects created by user making the request

You need to limit the queryset in your ModelForm. A ModelForm will thus look like:
from django import forms
class LinkForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['watched'].queryset = Watched.objects.filter(
link__user=user
)
class Meta:
model = Link
fields = ['url', 'watched']
In our view, we can then set the user object:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
#login_required
def some_view(request):
if request.method == 'POST':
form = LinkForm(request.POST, user=request.user)
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect('name-of-some-form')
else:
form = LinkForm(user=request.user)
return render(request, 'some-template.html', {'form': form})
For a class-based view, we can override the .get_form_kwargs(…) method [Djangod-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
class SomeView(LoginRequiredMixin, CreateView):
form_class = LinkForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)

Related

Django - Filtering Post Form option by currently logged-in User's Vehicle

I'm a django newbie and i'm making a form where a User can make a Post and pick one of his Vehicles for the Post. The Vehicle and the Post models are created like so:
*blog/models.py*
class Post(models.Model):
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE, null=True)
def get_absolute_url(self):
return reverse('post-detail', kwargs ={'pk': self.pk} )
*vehicles/models.py*
class Vehicle(models.Model)*:
TESLA = 'TESLA'
MAZDA = 'MAZDA'
VOLVO = 'VOLVO'
VEHICLE_CHOICES = (
(TESLA, "Tesla"),
(MAZDA, "Mazda"),
(VOLVO, "Volvo"),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
model = models.CharField(max_length=9,
choices=VEHICLE_CHOICES,
default=TESLA)
def __str__(self):
return self.model
My blog views:
*blog/views.py*
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = [ 'vehicle']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
I would like to filter the vehicles so that only the current logged in User's vehicles
show up in the form, i've tried a variety of different solutions but I seem to be going around in circles, if you could help me out that would be awesome. Thanks!
Since you are using createview, you can create a form in forms.py. First you have to send the logged in user to the form, then in the form, pop the user from kwargs and use it to filter the vehicles.
views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['vehicle']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['vehicle'].queryset = Vehicle.objects.filter(owner=user)

How to Query a ForeignKey field pointing to another ForeignKey field in Django

I have a model (Letter) with a foreign key, pointing to another model (Company) with a foreign key. Below is a simple schema from models.py
from django.contrib.auth.models import User
class Company (models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, editable=False)
date_created = models.DateField(default=timezone.now, null=True)
company_name = models.CharField(max_length=100, null=True)
class Letter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='letterhead_user', null=True)
company = models.ForeignKey(Company, related_name = "company_letter", on_delete=models.CASCADE, null=True)
subject = models.CharField(max_length=5000, null=True)
body = models.TextField()
I have created a form where users can create Letters with the model through ModelForm.
class LetterForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LetterForm, self).__init__(*args, **kwargs)
self.fields['company'].widget.attrs = {'class': 'input',}
self.fields['subject'].widget.attrs = {'class': 'input', 'placeholder': 'RE: ...'}
self.fields['body'].widget.attrs = {'class': 'textarea',}
class Meta:
model = Letter
fields = ('company', 'subject', 'body',)
The View:
def letter_form (request):
form = LetterForm()
if request.method == 'POST':
form = LetterForm(request.POST, request.FILES)
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect('letter')
Currently, when the user is presented with a form to create a Letter, on the Company field, all the companies from all the users appear. See the pic below:
Front end Form
I would like only the companies that the User has created to appear in the drop-down, not all companies from all users. Or to be able to select the first company that the User has created.
You can specify the logged in user in the form and filter accordingly. In the constructor of the form we thus limit the queryset with:
class LetterForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
self.fields['company'].widget.attrs = {'class': 'input',}
self.fields['subject'].widget.attrs = {'class': 'input', 'placeholder': 'RE: …'}
self.fields['body'].widget.attrs = {'class': 'textarea',}
if user is not None:
self.fields['company'].queryset = Company.objects.filter(user=user)
# …
and in the view, we then pass the logged in user to the constructor of the form:
from django.contrib.auth.decorators import login_required
#login_required
def letter_form(request):
if request.method == 'POST':
form = LetterForm(request.POST, request.FILES, user=request.user)
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect('letter')
else:
form = LetterForm(user=request.user)
# …
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
If you use a ModelForm for Letterit may look like this:
class LetterForm(forms.ModelForm):
class Meta:
model = Letter
fields = ["company", "subject", "body"]
# you need to init the form with the right user instance
def __init__(self, user=None, *args, **kwargs):
# call the default __init__ behavior
super().__init__(*args, **kwargs)
# this is the trick, you will filter the companies queryset here
if user:
self.fields['company'].queryset = Company.objects.filter(user=user)
So you need to pass down the user in the form from your view:
something like:
def my_view(request):
# assuming the user follow the standard Django user implementation
# and you user is logged in
form = LetterForm(request.POST or None, user=request.user)
if form.is_valid():
form.save()
# then redirect the user to whatever success page
# render the form
return render(request, "your_template.html", {"form": form})

Is there a more elegant way to implement an modification to save() method when using CreateView and ModelForm in Django?

I'm learning the Class-Based View and ModelForm of Django, and I feel so confused with those things.
I want to create a page where users can post articles.
My implementation is as following:
models.py
class Post(models.Model):
id = models.CharField(primary_key=True, null=False, max_length=20)
owner = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
content = models.TextField()
count_like = models.IntegerField(default=0)
created_time = models.DateTimeField()
last_modified = models.DateTimeField()
def save(self, *args, **kwargs):
''' On save, update last_modified '''
if not self.id:
count = Post.objects.count()
self.id = "PO" + str(count)
self.created_time = timezone.now()
self.last_modified = timezone.now()
return super(Post, self).save(*args, **kwargs)
def get_absolute_url(self):
print("pk"*100, self.pk)
return reverse('post_detail', kwargs={'pk': self.pk})
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['content']
def save(self):
return super().save(commit=False)
views.py
class PostCreateView(generic.CreateView):
model = Post
form_class = PostForm
template_name="post/create.jinja"
def form_valid(self, form):
self.object = form.save()
self.object.owner = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
urlpatterns
path('create/', PostCreateView.as_view(), name="post_create")
I checked and this works.
As you can see, the Post model has many attributes, but I just want users to fill 1 field content, the others would be automatically initiated. Is there any way to improve my implementation, because it's seperated into many places (model save() method, form save() method, valid_form() method).
One more question is what is self.object role? After assigned to a Post model instance, what would it be used for?
Please help me, if you don't understand what I say please ask in comment. Thanks ^^

IntegrityError at * NOT NULL constraint failed: main_post.owner_id

I want to create a PostModel(just like instagram) and while the form is created to connect the user to the model with One-to-one/foreign key relationship, anyway I'm getting a problem while trying to upload an image and the db doesn't updates.
I've tried this solution
...
# models.py
from django.contrib.auth.models import User
from django.conf import settings
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
description = models.CharField(max_length=255, blank=True)
image = models.ImageField(upload_to='images')
uploaded_at = models.DateTimeField(auto_now_add=True)
...
# forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('description', 'image', )
def save(self, commit=True):
if commit:
Post.save()
return Post
...
# views.py
def account(request):
post = PostForm(request.POST, request.FILES)
if request.method == "POST":
if post.is_valid():
post.save(commit=False)
post.owner = request.user
post.save(commit=True)
messages.success(request, f"you had successfully updated your profile image")
return redirect("main:account")
else:
for msg in form.error_messages:
messages.error(request, f"{msg}: {form.error_messages[msg]}")
return render(request = request,
template_name = "main/account.html",
context={'PostForm':post})
post = PostForm()
return render(request = request,
template_name = "main/account.html",
context={'PostForm':post})
You should not override the def save() method, this is fine as it is now, so:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('description', 'image', )
# no save
as for the view, you need to add the owner to the object, but here you are adding it to the form, and that thus has no effect (on the object):
from django.contrib.auth.decorators import login_required
#login_required
def account(request):
post = PostForm(request.POST, request.FILES)
if request.method == 'POST':
if post.is_valid():
post.instance.owner = request.user
post.save()
messages.success(request, f'you had successfully updated your profile image')
return redirect('main:account')
# …
I would also advise to rename post to post_form, since this is a form, not a post object.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Access editing profile only by profile owner using UserPassesTestMixin showing error?

i have managed to create profile pages for each user and every user should edit their own profile. in model, i have used AbstractUser model. and for the editing access i have imported UserPassesTestMixin.
here is my models.py:
class Profile(AbstractUser):
name=models.CharField(max_length=30, blank=True)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birthdate = models.DateField(null=True, blank=True)
here is my views.py:
class ProfileDetailView(DetailView):
template_name='profile.html'
model=Profile
def get_user_profile(self,pk):
return get_object_or_404(Profile,pk=pk)
class ProfileEditView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
model=Profile
template_name='profile_edit.html'
fields=['name','bio','birthdate','location','gender',]
login_url='login'
success_url = reverse_lazy('home')
def get_user_profile_edit(self,pk):
return get_object_or_404(Profile,pk=pk)
def test_func(self):
obj = self.get_object()
return obj.username == self.request.user
the problem is when the logged in user wants to edit it's profile it is showing 403 forbidden. no user can edit their profile. in test function what should is use to fix that?
Since the Profile is the user model, it means that obj should be request.user:
class ProfileEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model=Profile
template_name='profile_edit.html'
fields=['name','bio','birthdate','location','gender',]
login_url='login'
success_url = reverse_lazy('home')
def get_user_profile_edit(self,pk):
return get_object_or_404(Profile,pk=pk)
def test_func(self):
return self.get_object() == self.request.user
You however do not per se need this, you can simply use request.user as the object itself:
class ProfileEditView(LoginRequiredMixin, UpdateView):
model=Profile
template_name='profile_edit.html'
fields=['name','bio','birthdate','location','gender',]
login_url='login'
success_url = reverse_lazy('home')
def get_object(self, *args, **kwargs):
return self.request.user
In that case the view does not need a primary key, since people that visit the view will simply see their own profile to edit.