How Do I Access Data From a Multi-Table Inheritance Definition? - django

Straight from the Django Docs....
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
I can see the data in my database so I know it's working...but I can't figure out how to access the data. In the past I have used Class Based Views...this is my first go at using a multi-table inheritance structure beyond class based views.
I have tried to do...
def get_absolute_url(self):
return reverse("App:create_restaurant_detail",kwargs={'pk':self.object.place_ptr_id})
Here's my urls...
path("create_restaurant",views.CreateRestaurantView.as_view(), name='create_restaurant'),
path("create_restaurant_detail/<pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
And my Views....
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def get_success_url(self):
return redirect('App:create_restaurant_detail', kwargs={'pk':self.object.place_ptr_id})
class CreateRestaurantDetailView(LoginRequiredMixin,DetailView):
model = Restaurant
context_object_name = 'restaurant_detail'
template_name = 'create_restaurant_detail.html'
But the url lookup keeps saying not found.
In the log...I see....
django.urls.exceptions.NoReverseMatch: Reverse for 'create_restaurant_detail' with keyword arguments '{'kwargs': {'pk': 12}}' not found. 1 pattern(s
) tried: ['LevelSet/App/create_restaurant_detail/1bad5cba\\-a087\\-4f0a\\-9c3b\\-65ac096c3e42/(?P<pk>[^/]+)/$']
I'm trying to figure out how to access the data in the table. Thanks for any thoughts and help in advance.

class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def form_valid(self, form):
instance = form.save()
return JsonResponse({ 'id': instance.pk, 'success_url': self.get_success_url() })
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Main:main'))
else:
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
restaurant_instance = form.save()
self.object = restaurant_instance
return self.form_valid(form)
def get_success_url(self):
return reverse('Newapp:create_restaurant_detail', kwargs={ 'pk' : self.object.place_ptr_id })
My URL...
path("create_restaurant_detail/<int:pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
I misunderstood CreateView. This code is working at the moment, but I'm not sure JsonResponse is the best answer for form_valid. I couldn't figure out the proper alternative but my original problem has been solved. Thanks to Iain for helping me work through it.

Related

Django UpdateView user validation not working

Hello guys I have this update view where I am not able to validate the user(owner). How to tweak this to add that bit too.? Please have a look at the code.
class StoreInfoView(UpdateView, LoginRequiredMixin):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
def get_object(self, queryset=None):
obj = Store.objects.get(id=self.kwargs['id'])
if obj.user != self.request.user:
raise PermissionDenied('You Don\'t have permission to edit!')
return obj
def get(self, *args, **kwargs):
self.object = Store.objects.get(id=self.kwargs['id'])
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
Thanks
Instead of overriding like this, you can simply override get_queryset() method. Like this:
class StoreInfoView(UpdateView, LoginRequiredMixin):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(user=self.request.user)
In this way, non-owner users will get 404 error when they try to update.
Also, you do not need to override any other methods like get() and get_object() method.
The issue to your problem is the order of inheritance. When you will go through the official docs for LoginRequiredMixin, you will find this
This mixin should be at the leftmost position in the inheritance list.
Please update your code to this
class StoreInfoView(LoginRequiredMixin, UpdateView):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
...
Notice, now LoginRequiredMixin is put before the UpdateView. This should resolve your query.
I hope it helps. :)

Unable to restrict access to the post edit screen with django

I am restricting access to the posted edit screen.
I want to make sure that only the user who posted or the super user can edit the post.
For that purpose, "UserPassesTestMixin" is used.
However, there is no limit.
And I think that my own "OnlyRecordEditMixin" is not responding. If you understand, thank you.
#mixin
class OnlyRecordEditMixin(UserPassesTestMixin):
raise_exception = True
def test_func(self):
user = self.request.user
id = self.kwargs['id']
print(id)
return user.pk == URC.objects.get(id=id).user or user.is_superuser
#view
class RecordDetailEdit(UpdateView,OnlyRecordEditMixin):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
def get_object(self, queryset=None):
obj = URC.objects.get(id=self.kwargs['id'])
return obj
#url
path('<id>/edit/', views.RecordDetailEdit.as_view(), name='record_detail_edit'),
#model
class URC(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
A mixin should be put before the base view class, to construct a good MRO here.
class RecordDetailEdit(OnlyRecordEditMixin, UpdateView):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
You should not override the get_object(..) method here. The get_object(..) method is defined in terms of get_queryset(..): it filters the get_queryset(..) with the given primary key (and/or slug).
You can however easily restrict access by restricting the get_queryset:
class OnlyRecordEditMixin(LoginRequiredMixin):
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(user=self.request.user)
return qs
or even more generic:
class OnlyRecordEditMixin(LoginRequiredMixin):
user_field = 'user'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(**{self.user_field: self.request.user})
return qs
Here we can thus change the user_field to point to the name that is the owner of the object.
This will return a HTTP 404 response if a user that is not a superuser, nor the "owner" of the object aims to update that object.
This is cheaper, since you prevent fetching the object multiple times.

How to make sure users only get to DetailView, Listview and UpdateView their own created objects

I've created a simple app where logged in users can can submit a session for a conference, view the results of their submission, view a list of their submissions, and edit their submissions (they should not have access to other users' submissions). I'm using django's class-based views (CreateView, DetailView, ListView, UpdateView).
I'm struggling with the permissions however. All the views, except updateview, work but if I type in the url directly using a non-logged in username I can see their submissions.
I also suspect that the permissions is the same reason I can't get the updateview to work.
What am I missing? And is there a better way to avoid using usernames and slugs in the Url? I can't seem to find any examples or tips in how to do this type of thing. I'm a beginner so probably miss some understanding of the fundamentals here and there.
I've tried to understand how the User model works because there I did manage to find a way to create, view and edit user details in a protected way. I relied on function views there though and can't seem to apply that approach to the submission app.
models.py
class Hsession(models.Model):
submitter = models.ForeignKey(User, related_name="submittersessions", on_delete=models.CASCADE)
submission_date = models.DateTimeField(auto_now=True)
session_title = models.CharField("session title", max_length=40, default='')
session_description = models.TextField("session description", max_length=350, default='')
slug = models.SlugField(allow_unicode=True, unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.session_title)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("submission:detail-single", kwargs={"username": self.submitter.username, "slug": self.slug})
urls:
urlpatterns = [
path("", views.CreateSubmission.as_view(), name="create"),
path("by/<username>/<slug>",views.SubmissionDetail.as_view(),name="detail-single"),
path("by/<slug>/edit",views.EditSubmission.as_view(), name="edit"),
path("by/<username>/",views.SubmissionList.as_view(), name="list"),
]
views.py
class CreateSubmission(LoginRequiredMixin, generic.CreateView):
fields = ('session_title', 'session_description', 'subject_category')
model = models.Hsession
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.submitter = self.request.user
self.object.save()
return super().form_valid(form)
class SubmissionList(LoginRequiredMixin, generic.ListView):
model = models.Hsession
template_name = "submission/user_hsession_list.html"
def get_queryset(self):
try:
self.hsession_submitter = User.objects.prefetch_related("submittersessions").get(
username__iexact=self.kwargs.get("username")
)
except User.DoesNotExist:
raise Http404
else:
return self.hsession_submitter.submittersessions.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["hsession_submitter"] = self.hsession_submitter
return context
class SubmissionDetail(LoginRequiredMixin, generic.DetailView):
model = models.Hsession
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(
submitter__username__iexact=self.kwargs.get("username")
)
class EditSubmission(LoginRequiredMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
forms.py
class UserSubmissionForm(ModelForm):
class Meta:
model = Hsession
fields = ['session_title','session_description', 'subject_category']
class EditSubmissionForm(ModelForm):
class Meta:
model = Hsession
fields = ['session_title','session_description', 'subject_category']
You should use UserPassesTestMixin like this
from django.contrib.auth.mixins import UserPassesTestMixin
class EditSubmission(UserPassesTestMixin,LoginRequiredMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
def test_func(self):
//should return true if he have access
if self.request.user.is_authenticated:
slug = self.kwargs['slug']
obj = self.model.objects.get(slug=slug)
login_user = self.request.user
return login_user.pk == obj.submitter.pk
else:
return False
for more information UserPassesTestMixin
from django.contrib.auth.mixins import UserPassesTestMixin
class EditSubmission(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
def test_func(self):
self.object = self.get_object()
if self.request.user == self.object.user:
return True
else:
return False
I'm using "UserPassesTestMixin" like this and it is working.

Django CreateView- ModelForm with many-to-many InlineFormset ValueError

I'm struggling with such a problem:
I have models:
class ForecastType(models.Model):
client = models.ForeignKey(Client, related_name="weatherforecast_client")
created_by = models.ForeignKey(Forecaster, related_name="weatherforecast_created_by")
modified_by = models.ForeignKey(Forecaster,
related_name="weatherforecast_modified_by",
blank=True,
null=True)
creation_date = models.DateTimeField(auto_now_add=True)
modification_date = models.DateTimeField(auto_now=True)
weather_forecasts = models.ManyToManyField('WeatherForecast')
STATUS_CHOICES = (
("D", "Draft"),
("A", "Active"),
("H", "History"),
)
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default="D")
class OneDayForecast(ForecastType):
def __str__(self):
return f"Prognoza pogody dla: {self.client}, wykonana dnia: {self.creation_date}"
class WeatherForecast(models.Model):
begin_date = models.DateTimeField(blank=True, null=True)
finish_date = models.DateTimeField(blank=True, null=True)
forecast_type = models.ForeignKey('ForecastType', null=True, blank=True)
description = models.TextField(max_length=300, blank=True, null=True)
I also have ModelForm and InlineFormset:
class OneDayForecastForm(ModelForm):
class Meta:
model = OneDayForecast
exclude = ('weather_forecasts',)
WeatherForecastFormset = inlineformset_factory(OneDayForecast, WeatherForecast, exclude=('forecast_type',), extra=2)
and finally an CreateView:
class OneDayForecast(ForecasterRequiredMixin, CreateView):
template_name = "forecaster/one_day.html"
success_url = reverse_lazy("forecaster:dashboard")
model = OneDayForecast
form_class = OneDayForecastForm
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
weather_forecast_form = WeatherForecastFormset()
return self.render_to_response(
self.get_context_data(form=form, weather_forecast_form=weather_forecast_form)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
weather_forecast_form = WeatherForecastFormset(self.request.POST)
if form.is_valid() and weather_forecast_form.is_valid():
return self.form_valid(form, weather_forecast_form)
else:
return self.form_invalid(form, weather_forecast_form)
def form_valid(self, form, weather_forecast_form):
self.object = form.save(commit=False)
for weather_form in weather_forecast_form:
weather_object = weather_form.save()
self.object.weatherforecast_set.add(weather_object)
self.object.save()
form.save_m2m()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, weather_forecast_form):
return self.render_to_response(
self.get_context_data(form=form, weather_forecast_form=weather_forecast_form)
)
After trying to submit my Form with it's InlineFormset I receive this error:
Request Method: POST
Request URL: http://localhost:8000/forecaster/my-clients/6/one_day/
Django Version: 1.11
Exception Type: ValueError
Exception Value:
Unsaved model instance <OneDayForecast: Forecast for: client1> cannot be used in an ORM query.
Problem probably lies in commit=False in form_valid method but I have no clue how to repair it.
Does anyone know to solve this?
Thanks.
Okay, so I think that there are a couple problems here, both in your post and form_valid() methods. I've referred to my own implementations of inline formsets to see what you do differently.
First of all, I believe that the first line of the post method should be self.object = self.get_object().
Second, weather_forecast_form = WeatherForecastFormset(self.request.POST) should be weather_forecast_form = WeatherForecastFormset(self.request.POST, instance=self.object).
Notice the relationship here between the object we get and then using it at the instance in the formset. That's all for the post method.
Now, in my own implementation, I have many formsets, so I loop through each formset as follows (you can use exactly the same code if you put your formset into a list and pass it to form_valid):
def form_valid(self, form, formsets):
self.object = form.save()
for formset in formsets:
formset.instance = self.object
formset.save()
return HttpResponseRedirect(self.get_success_url())
Notice that we fully save the parent form here, including committing it. We then save all formsets. If you wanted to keep your single formset, you can easily change the above code to the following:
def form_valid(self, form, weather_forecast_form):
self.object = form.save()
weather_forecast_form.instance = self.object
weather_forecast_form.save()
return HttpResponseRedirect(self.get_success_url())
The error that you report at the bottom of your question is a direct result of form.save(commit=False). What is happening there is that you are "pretend" saving the parent, and then trying to fully save the children. The database doesn't have record of the parent, so it spits out that error. Committing before saving many to many records is a must (at least in my experience).

How to perform queries in django modelform?

I tried this in my modelform:
class Ledgerform(forms.ModelForm):
class Meta:
model = ledger1
fields = ('name', 'group1_Name')
def __init__(self, User, Company, *args, **kwargs):
self.User = kwargs.pop('User', None)
self.Company = kwargs.pop('Company', None)
super(Ledgerform, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = {'class': 'form-control',}
self.fields['group1_Name'].queryset = group1.objects.filter(User= self.User,Company = self.Company)
In my views.py I have done something like this:
class ledger1ListView(LoginRequiredMixin,ListView):
model = ledger1
paginate_by = 15
def get_queryset(self):
return ledger1.objects.filter(User=self.request.user, Company=self.kwargs['pk'])
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def form_valid(self, form):
form.instance.User = self.request.user
c = company.objects.get(pk=self.kwargs['pk'])
form.instance.Company = c
return super(ledger1CreateView, self).form_valid(form)
I want to perform the the same query that I have passed in my ledger1ListView by using queryset in my modelform but my kwargs.pop is not returning the current user or the company...
This is my models.py:
class ledger1(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
Company = models.ForeignKey(company,on_delete=models.CASCADE,null=True,blank=True,related_name='Companys')
name = models.CharField(max_length=32)
group1_Name = models.ForeignKey(group1,on_delete=models.CASCADE,blank=True,null=True)
Do any one know what I am doing wrong in my code?
Thank you in advance
You can override the FormMixin.get_form_kwargs [Django-doc] in your view, that it constructs a dictionary with the parameters necessary to initialize the form, like:
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def get_form_kwargs(self):
data = super(ledger1CreateView, self).get_form_kwargs()
data.update(
User=self.request.User,
Company=company.objects.get(pk=self.kwargs['pk'])
)
return data
The form_valid function is called after the form is constructed, validated and appears to be valid. Typically it is used to redirect the user to the "success page".