Django - dynamic model for TemplateView class-based - django

I want to switch between two models depending on which route I am in.
I am overwriting get_queryset() function to return the correct model:
class DynamicModelView(TemplateView, PageDescriptionListingMixin):
model = None
template_name = 'dynamic_model.html'
context_object_name = "accounts"
def get_context_data(self, **kwargs):
context = super(DynamicModelView, self).get_context_data(**kwargs)
self.add_page_text_to_context(context)
return context
def get_queryset(self):
if '/dynamic_user/' in self.request.path:
model = UserAccount
else:
model = AdminAccount
return model.objects.first()
As you can see in get_context_data I am injecting an object in context for AdminAccount but inside template I can't see it! in fact if I changed model from None to AdminAccount then it appears which I want that to happen dynamically.
Is there any way to switch models dynamically in Django?

override dispatch method.
def dispatch(self, request, *args, **kwargs):
if not self.model:
self.model = <MODEL>
return super(DynamicModelView, self).dispatch(request, *args, **kwargs)

Related

Attibutes are 'disappearing' from CBV. Django

I am trying to build CBV with class View parent. This view takes slug of object and find that object between two django models. The functions from services.py was doing a lot of DB queries, so, I tried to reduce them by giving to FeedbackSection necessary attributes(slug, model_instance and context) and lately override them in get method.
class FeedbackSection(View):
"""
Feedback section for different objects.
This is something like 'generic' view, so I implement it that it will find
the model and feedbacks for this model by having only slug.
"""
template_name = 'feedbacks/feedback-section.html'
form_class = CreateFeedbackForm
slug = None
model_instance = None
context = None
def get(self, request, *args, **kwargs):
self.slug = kwargs.get('slug')
self.model_instance = get_model_instance(self.slug)
self.context = get_feedback_section_context(self.slug, self.form_class, self.model_instance)
return render(request, self.template_name, self.context)
#method_decorator(login_required)
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# will create feedback object and update model[Advert, Company] rating.
end_feedback_post_logic(self.request.user, form, self.model_instance)
return render(request, self.template_name, self.context)
The attributes(slug, model_instance and context), when post method is in runtime are equivalent to None.
The problem is that this implementation was working fine yesterday, but today it's not.
I know I can use my functions again, but in post method. I don't want to do this. Because it will multiple DB Queries by two.
We need to override the setup method of the View class and define those attributes there.
def setup(self, request, *args, **kwargs):
self.slug = kwargs.get('slug')
self.model_instance = get_model_instance(self.slug)
self.context = get_feedback_section_context(
self.slug,
self.form_class,
self.model_instance
)
return super().setup(request, *args, **kwargs)

Django Class Based Views handle multiple queries

I'm rewriting my django function view to class based views. I have this current function
#login_required
def settings(request, template_name="settings.html"):
context = {}
context['kcs'] = KlarnaProfile.objects.filter(user_profile__user=request.user)
context['extends'] = ExtendProfile.objects.filter(user_profile__user=request.user)
context['fortnoxs'] = FortnoxProfile.objects.filter(user_profile__user=request.user)
return render(request, template_name, context)
that confirms first if a user is logged in and then get's information linked to that user account
here's what I've got as my class based view
class SettingsView(TemplateView):
template_name = "settings.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
how can I add the three filters that use the logged in user as a filter?
Use get_context_data method like this:
class SettingsView(TemplateView):
...
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
context_data['kcs'] = KlarnaProfile.objects.filter(user_profile__user=self.request.user)
...
return context_data
Neat pick. Instead of doing:
class SettingsView(TemplateView):
...
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
You can do:
#method_decorator(login_required, name='dispatch')
class SettingsView(TemplateView):
...
You might want to read Decorating the class from Django's official documentation.

Django: AttributeError: view object has no attribute 'kwargs'

I am building a view that I am passing in a uuid from the url. However when I try to access the kwarg, I get a "AttributeError: view object has no attribute 'kwargs'" error.
In my template, I am passing a UUID:
create/97261b96-23b8-4915-8da3-a90b7a0bdc8e/
The URL:
re_path(
r"^create/(?P<uuid>[-\w]+)/$",
views.DetailCreateView.as_view(),
name="detail_create"),
The View:
class DetailCreateView(SetHeadlineMixin, LoginRequiredMixin, InlineFormSetView):
inline_model = Detail
headline = "Create a Detail"
form_class = DetailForm
success_message = "Detail Added"
template_name = "details/detail_create.html"
def get_object(self, **kwargs):
return Post.objects.get_subclass(uuid=self.kwargs.get('uuid'))
def __init__(self, *args, **kwargs):
super(DetailCreateView, self).__init__(*args, **kwargs)
self.object = self.get_object()
self.model = self.object.__class__()
For context on what is happening -
Post is a model that is an InheritanceManager that other models (Product & Variation) inherit from.
Both models Product & Variation have a manytomanyfield to Detail.
Upon creating a Detail, I will be adding it to either the Product object or Variation object.
To set the model for the InlineFormSetView, I am trying to use the UUID to query for the object and dynamically set that based upon the class of the object I am trying to create a Detail for.
Question
Any ideas why I can't access the kwargs which is being sent in the URL path?
In as_view method kwargs and args attributes are assigned to the view after __init__ method. So when you call get_object inside __init__ it raises the error since self.kwargs is not assigned yet. To fix this error you can move
self.object = self.get_object()
self.model = self.object.__class__()
from __init__ to get_object:
class DetailCreateView(SetHeadlineMixin, LoginRequiredMixin, InlineFormSetView):
inline_model = Detail
headline = "Create a Detail"
form_class = DetailForm
extra = 10
success_message = "Detail Added"
template_name = "details/detail_create.html"
def get_object(self, **kwargs):
self.object = Post.objects.get_subclass(uuid=self.kwargs.get('uuid'))
self.model = self.object.__class__()
return self.object
def __init__(self, *args, **kwargs):
super(DetailCreateView, self).__init__(*args, **kwargs)
Try to use self.request.query_params.get('uuid')

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

How to pass ForeignKey value into initial data for Django form

I have a model like this:
class Job(models.Model):
slug = models.SlugField()
class Application(models.Model):
job = models.ForeignKey(Job)
And a view like this:
class ApplicationCreateView(CreateView):
model = Application
A user will view the job object (/jobs/<slug>/), then complete the application form for the job (/jobs/<slug>/apply/).
I'd like to pass application.job.slug as the initial value for the job field on the application form. I'd also like for the job object to be put in context for the ApplicationCreateView (to tell the user what job they're applying for).
How would I go about doing this in my view?
You may be interested in CreateView page of the fantastic http://ccbv.co.uk/ In this page, you can see in one glance which member methods and variables you can use.
In your case, you will be interested to override:
def get_initial(self):
# Call parent, add your slug, return data
initial_data = super(ApplicationCreateView, self).get_initial()
initial_data['slug'] = ... # Not sure about the syntax, print and test
return initial_data
def get_context_data(self, **kwargs):
# Call parent, add your job object to context, return context
context = super(ApplicationCreateView, self).get_context_data(**kwargs)
context['job'] = ...
return context
This has not been tested at all. You may need to play with it a little. Have fun.
I ended up doing the following in a function on my class:
class ApplicationCreateView(CreateView):
model = Application
form_class = ApplicationForm
success_url = 'submitted/'
def dispatch(self, *args, **kwargs):
self.job = get_object_or_404(Job, slug=kwargs['slug'])
return super(ApplicationCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
#Get associated job and save
self.object = form.save(commit=False)
self.object.job = self.job
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, *args, **kwargs):
context_data = super(ApplicationCreateView, self).get_context_data(*args, **kwargs)
context_data.update({'job': self.job})
return context_data