how to use update_or_create in django createview - django

I want to update_or_create object by django CreateView.
my class problem is update object but create duplicate of updated object
class CardexCreateView(LoginRequiredMixin, CreateView):
model = Cardex
fields = '__all__'
template_name = 'cardex_add.html'
def get_success_url(self):
return reverse('cardexes-list')
def form_valid(self, form):
product = Stock.objects.get(pk=1)
self.object, _ = Cardex.objects.update_or_create(
product=product, defaults={'standard_weight': 10}
)
return super(CardexCreateView, self).form_valid(form)

Related

Create form to change relationship from related model's form

I have two models:
class Thing(forms.ModelForm):
class Owner(forms.ModelForm):
thing = models.OneToOneField(Thing)
I want to add a form to change the owner in Thing's UpdateView. I can do it like this:
class ThingForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Owner.objects.all(),
)
class Meta:
model = Thing
fields = '__all__'
And then process the result inside form_valid() method. But isn't there a more direct approach for this, where i just add this to the fields of the form?
UPDATE
So I ended up doing it like this:
class ThingUpdateView(UpdateView):
model = Thing
form_class = ThingForm
def get_initial(self):
initial = super(ThingUpdateView, self).get_initial()
try:
initial['owner'] = self.object.owner
except Thing.owner.RelatedObjectDoesNotExist:
pass
return initial
def form_valid(self, form):
self.object = form.save(commit=False)
owner = form.cleaned_data['owner']
owner.thing = self.object
owner.save(update_fields=['thing'])
self.object.save()
return redirect(self.object.get_absolute_url())
Maybe there's a better way.

overwrite form for django CreateView

Perhaps just another stupid beginner question:
I am having trouble overwriting a django model form which is currently using the following code in views.py.
The code below works well !
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
### want this instead of line 1 and 2 above --> form_class = PostCreateForm()
view_args = collections.namedtuple('view_args', ['page_title'])
view_args = view_args(page_title="Create Post")
def get_context_data(self, *args, **kwargs):
self.tgt_url_args = MyHelper.parse_tgt_url(self)
context = super(PostCreateView, self).get_context_data(*args, **kwargs)
context.update(MyHelper.get_context_metadata(self, self.tgt_url_args, PostCreateView.view_args))
return context
def form_valid(self, form):
form.instance.author = self.request.user
tgt_url_args = MyHelper.parse_tgt_url(self)
blog_article = Article.objects.get(pk=tgt_url_args.get('Article', '0'))
form.instance.article_field = blog_article
return super().form_valid(form)
However, I would like to overwrite the form in order to add placeholder to the fields. So I replace line 1 and 2 by:
form_class = PostCreateForm()
I also added the follwing in forms.py:
from django import forms
from . models import Post
class PostCreateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
print('imported PostCreateForm')
When I import and run this however, I get the following error:
'PostCreateForm' object is not callable
What am I doing wrong here?
You should not pass a constructed form, only a reference to the form class:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostCreateForm # no parenthesis
# ...
Django will thus each time construct a new form (depending on the situation with request.POST, etc.).
If you need to pass extra parameters to the form, you can specify a dictionary in get_form_kwargs [Django-doc].

django fill form field automatically from context data

I have a form attached to a DetailedView and its working fine when saved. I would like the form field(position) to be prepopulated with the value coming from the slug of the detailed view(e.g jobs/human-resource-manager). The Model of the form field has a Foreignkey to the JobPost model. Need help. Part of my view looks like this
class JobsDetailView(DetailView):
model = JobPost
template_name = 'job_post-detail.html'
def get_context_data(self, **kwargs):
context = super(JobsDetailView, self).get_context_data(**kwargs)
context['position'] = JobPost.objects.order_by('position')
context['job_app_form'] = JobsForm()
return context
foms.py
from django import forms
from job_post.models import JobsApplied
class JobsForm(forms.ModelForm):
class Meta:
model = JobsApplied
fields = '__all__'
def form_valid(self, form):
form.instance.customuser = self.request.user
return super().form_valid(form)
I'm assuming you do not want your users to be able to interact with or change these prefilled values.
I'm making a comments/review model and I want it to automatically link reviews to the people they are about
models.py
class Review(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
...
I hide the person field in the ReviewsForm to prevent user input by either omitting it from the 'fields' or adding it to an 'exclude'.
forms.py
class ReviewsForm(forms.ModelForm):
class Meta:
model = Review
fields = ('rating', 'summary', 'review_text')
Then, when processing the form in the view, I use commit=False so I can manipulate field values before saving to the database.
Include prefilled values, save and then redirect the user wherever is ideal
views.py
def person(request, area_slug, category_slug, person_id):
...
if form.is_valid():
pending_review = form.save(commit=False)
pending_review.person = Person.objects.get(pk = person_id)
pending_review.save()
return HttpResponseRedirect(...)
django fill form field automatically from context data for django form and django formsets
For formsets in forms.py
StoreRequestAccessoryUpdateFormSet = forms.modelformset_factory(StoreRequestAccessory, form=StoreRequestAccessoryUpdateForm, exclude=["storeRequestId"], can_delete=True)
In get_context_data you can add it as you like for django
class StoreRequestUpdateView(LoginRequiredMixin, UpdateView):
template_name = "Inventory/Stock/StoreRequest/StoreRequestUpdateView.html"
model = StoreRequest
fields = ["fromStoreId", "toStoreId", "reference", "status", "remark"]
def get_context_data(self, **kwargs):
context = super(StoreRequestUpdateView, self).get_context_data(**kwargs)
print(self.object.pk)
context.update({
# "StoreRequestForm": context.get("form"),
"StoreRequestForm": StoreRequestUpdateForm(instance=StoreRequest.objects.get(id=self.object.pk)),
"StoreRequestAccessoryForm": StoreRequestAccessoryUpdateFormSet(
queryset=StoreRequestAccessory.objects.filter(storeRequestId=self.object.pk),
prefix="storereq_accessory_form"),
})
return context

Filtering a model in a CreateView with get_queryset

I'm trying to filter a model with get_queryset() and it seems to work in the view but not in the template.
My view :
class FolderCreate(CreateView):
fields = ['name', 'parent']
template_name = 'Form/folder_create.html'
def get_queryset(self):
folders = Folder.objects.filter(owner=self.request.user)
print folders # ==> return [<Folder: Folder>, <Folder: Another folder>]
return folders
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
return super(FolderCreate, self).form_valid(form)
def get_initial(self):
if self.request.method == 'GET':
foldersUrl = self.request.META['HTTP_REFERER'].split('/')
foldersUrl.pop()
folder = urllib2.unquote(foldersUrl[-1])
try:
return {'parent' : Folder.objects.get(name=folder, owner=self.request.user)}
except Folder.DoesNotExist:
pass
As you can see, folders return two objects related to the session user in get_queryset() : 'Folder' and 'Another folder
Infortunately, the combobox of my template get all the folders, without any filtering.
Any idea ?
The issue here is that get_queryset is not used in a CreateView, as it's meant for filtering the models returned for display in a list or detail view. You want something completely different: you want to filter the choices available in a form field.
To do that you will need to create a custom ModelForm that accepts a user kwarg and filters the queryset accordingly:
class FolderForm(forms.ModelForm):
class Meta:
model = Folder
fields = ['name', 'parent']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(FolderForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = Folder.objects.filter(user=user)
and then change your view to use that form and pass in the user parameter:
class FolderCreate(CreateView):
template_name = 'Form/folder_create.html'
form_class = FolderForm
def get_form_kwargs(self):
kwargs = super(FolderCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs

How can I change the queryset of one of the fields in the form I'm passing to inlineformset_factory

I'm using django extra views:
# views.py
from django.forms.models import inlineformset_factory
from extra_views import (CreateWithInlinesView, UpdateWithInlinesView,
InlineFormSet, )
class LinkInline(InlineFormSet):
model = Link
form = LinkForm
extra = 1
def get_form(self):
return LinkForm({})
def get_formset(self):
return inlineformset_factory(self.model, self.get_inline_model(), form=LinkForm, **self.get_factory_kwargs())
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
I want this 'keywords' field to change based on the pk I pass to the view through the url.
# forms.py
class LinkForm(forms.ModelForm):
keywords = forms.ModelMultipleChoiceField(queryset=ClientKeyword.objects.filter(client__pk=1))
class Meta:
model = Link
I could manage to overwrite the form's init, however:
I don't have access to self.kwargs inside LinkInline
Even if I did, I'm not sure I can pass an instantiated form to inlineformset_factory()
Ok, if any poor soul needs an answer to how to accomplish this... I managed to do it by overwriting construct_inlines() (which is part of extra_views.advanced.ModelFormWithInlinesMixin) and modifying the field's queryset there.
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
def construct_inlines(self):
'''I need to overwrite this method in order to change
the queryset for the "keywords" field'''
inline_formsets = super(TargetCreateView, self).construct_inlines()
inline_formsets[0].forms[0].fields[
'keywords'].queryset = ClientKeyword.objects.filter(
client__pk=self.kwargs['pk'])
return inline_formsets
def forms_valid(self, form, inlines):
context_data = self.get_context_data()
# We need the client instance
client = context_data['client_obj']
# And the cleaned_data from the form
data = form.cleaned_data
self.object = self.model(
client=client,
budget=data['budget'],
month=data['month']
)
self.object.save()
for formset in inlines:
f_cd = formset.cleaned_data[0]
print self.object.pk
link = Link(client=client,
target=self.object,
link_type=f_cd['link_type'],
month=self.object.month,
status='PEN',
)
# save the object so we can add the M2M fields
link.save()
for kw in f_cd['keywords'].all():
link.keywords.add(kw)
return HttpResponseRedirect(self.get_success_url())