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)
Related
I have a ModelForm which needs a user passed in so that the queryset can be updated. I am overriding the __init__ method of the ModelForm as such:
def __init__(self, *args, **kwargs):
# override init to get user's casino's EmployeeType queryset
self.user = kwargs.pop('user')
print(self.user)
super(MemoForm, self).__init__(*args, **kwargs)
self.fields['receiver'].queryset = EmployeeType.objects.filter(
casino=self.user.casino
)
In the View I have a get and a post method. I am trying to pass the **kwargs in as such:
class VideoUploadView(LoginRequiredMixin, View):
"""
Display a form for uploading videos.
"""
form_class = VideoUploadForm
success_url = '/videos'
template_name = 'videos/video_upload.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(
request,
self.template_name,
{'form': form, 'user': self.request.user}
)
In a CreateView you are able to use the get_form_kwargs method to pass in the **kwargs. How is it done in a normal View? Should we use the __init__ method? The way shown above does not seem to work as both *args and **kwargs seem to be empty.
These are the built-in methods of View.
I don't really understand why you're not using a FormView here as well, so that you can still override get_form_kwargs; you really shouldn't ever need to define get (or post) directly.
But nevertheless, the answer is simple: you just pass your kwargs directly to the form:
form = self.form_class(user=request.user)
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().
I have to specify a success_url, otherwise I get an error. So how to specify it, in order to stay to the same page?
Also, is everything else correct regarding the SearchView, beucase I have a feeling that something is missing. My context should be composed by form, query, concepts, language and languages.
Thanks
urls.py
url(r'^(?P<langcode>[a-zA-Z-]+)/search/$', SearchView.as_view(), name='search').
views.py
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
class SearchView(_LanguageMixin, FormView):
template_name = "search.html"
form_class = SearchForm
success_url = #......
query = ''
concepts = []
def get_initial(self):
return {'langcode': self.langcode}
def get_context_data(self, **kwargs):
context = super(SearchView, self).get_context_data(**kwargs)
context.update({"query": self.query, "concepts": self.concepts})
return context
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return super(SearchView, self).form_valid(form)
[EDIT]
I did this:
def get_success_url(self):
return reverse('search', kwargs={'langcode': self.langcode})+"?query={}".format(self.query)
The form renders, but whenever I search for anything, I get back the empty search text field. And the URL looks something like this: http://localhost:8000/en-US/search/?query=asd
By default, a FormView (actually, any subclass of ProcessFormView) will return a HttpResponseRedirect in form_valid. As you are calling the super's method in your form_valid method, you also return a HttpResponseRedirect. In the process, the actual POST data is lost, and though you pass it as a GET parameter, it is not used in the actual form.
To fix this, you need to not call super in your form_valid method, but instead return a rendered template in a HttpResponse object, e.g.:
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return self.render_to_response(self.get_context_data(form=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
I have the following (simplified) models:
class Idea(models.Model):
tagline = models.TextField()
class Lot(models.Model):
address = models.CharField()
...other fields...
ideas = models.ManyToManyField(Idea)
I want to display a Lot detail page that lists all the info about the lot, including ideas associated with that lot. This is simple to do.
However, in addition, I want the user to be able to add a new idea for that lot from this page. Upon submission, the user should return to the Lot detail page with their new idea now part of the list.
I've tried inline formsets for the new idea, but that only shows up as a drop down of existing ideas, it does not allow for a new idea to be created. Plus, it seems overkill as I only need the user to be able to add a new idea, not edit/remove ideas already submitted. And I also don't need them to be able to edit the other lot information, only add a related idea.
I know there is probably a simple way to achieve this, but I'm a bit stuck at the moment.
Any help would be appreciated.
Thanks!
EDIT: I'm not referring to the Django admin. This is for user facing forms.
There should be a clickable green + mark next to your foreign key / M2M fields. That will allow you to create a new Idea and then return to your Lot instance.
Here's an example (using filter_horizontal for the ManyToManyField):
Here's a solution I found to work: (ref: https://docs.djangoproject.com/en/1.5/topics/class-based-views/mixins/#using-formmixin-with-detailview)
class LotDisplay(DetailView):
model = Lot
def get_context_data(self, **kwargs):
context = super(LotDisplay, self).get_context_data(**kwargs)
context['form'] = IdeaForm()
return context
class LotAddIdeaView(FormView, SingleObjectMixin):
model=Lot
form_class = IdeaForm
template_name = 'lotxlot/lot_detail.html'
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
request.session['post'] = request.POST
url = "%s?next=%s" % (reverse('account_login'), request.path)
return HttpResponseRedirect(url)
else:
self.object = self.get_object()
return super(LotAddIdeaView, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('lotxlot_lot_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
"""
Auto-populate user
and save form.
"""
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
instance.lots.add(self.object)
instance.save()
return HttpResponseRedirect(self.get_success_url())
class LotDetailView(View):
def get(self, request, *args, **kwargs):
view = LotDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = LotAddIdeaView.as_view()
return view(request, *args, **kwargs)