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)
Related
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)
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 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'm writing a very simple Comment app. For a bunch of reasons, I need to write my own here instead of using a great product like Disqus.
So, I have a page and on that page, special users can make comments.
I've got everything working except adding the page id to the form submission.
I keep getting key errors when I try to use a hidden form field and form.cleaned_data.
So now I'm passing the page_id via the URL.
My urls.py
# Submit a comment
url(regex=r'^comment/create/(?P<page_id>\d+)/$',
view=CommentCreate.as_view(),
name='comment_create',
),
And my views.py
class CommentCreate(CreateView):
success_url = reverse_lazy('comment_submitted')
model = Comment
template_name='learnd/comment_submitted.html'
form_class = CommentCreateForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.page = self.kwargs['page_id']
self.object.save()
return HttpResponseRedirect(self.get_success_url())
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(CommentCreate, self).dispatch(*args, **kwargs)
The error I'm getting is:
Cannot assign "u'1'": "Comment.page" must be a "Page" instance.
And that's where I'm getting lost.
I think the problem is that kwargs is retuning the dictionary with u:1 but I can't figure out how to access just the 1 to pass to my view.
Any help is welcome.
Thanks.
You need to look up the Page object before assigning it:
def form_valid(self, form):
page = get_object_or_404(Page, pk=self.kwargs.get('page_id'))
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.page = page
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Alternatively you could do self.object.page_id = self.kwargs['page_id'] but you run the risk of the passed in page_id not being a valid Page and the object.save() will throw an exception.
I have an app that has two models in it. The first is called scenario. The second is called solutions. There is a M2M between scenarios and solutions. You can have any number of solutions belonging to scenarios and visa versa. Pretty straightforward.
The trick is, everything is filtered on the user. So I don't want to see the solutions or scenarios that other's have when I'm working.
I've written a view that is a CreateView. It adds a filtered list of solutions to the context. Once it's in the context, I loop it in the template to get the id. I add that to the form.
The problem I'm having is in the form_valid method.
How do I add the selected items to the save, so that they are added to the scenario?
Here's my view:
class ScenarioCreate(CreateView):
success_url = reverse_lazy('scenario_list')
template_name = "gps/create_scenario_form.html"
model = Scenario
form_class = ScenarioCreateForm
def get_context_data(self, **kwargs):
context = super(ScenarioCreate, self).get_context_data(**kwargs)
context['solution_list'] = Solution.objects.filter(user=self.request.user)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.solutions = form.cleaned_data['solutions'] # <= what goes here?
self.object.save()
form.save_m2m()
return HttpResponseRedirect(self.get_success_url())
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ScenarioCreate, self).dispatch(*args, **kwargs)
I had assumed that cleaned_data would magically solve my set issue.
I have two questions.
How do I get that save to work?
Am I think about adding to the context correctly, or is there a better way?
All help appreciated.
UPDATE: Adding ScenarioCreateForm
class ScenarioCreateForm(ModelForm):
class Meta:
model = Scenario
exclude = ('user', 'created')