Django save multiple foreign keys to database with multiple upload - django

I am a newbie in Django and I am looking for the best workaround on how to deal with this problem.
I have these 3 Models:
class Rigs(models.Model):
name = models.CharField(max_length=30)
featured_img = models.ImageField(upload_to='images/')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Gallery(models.Model):
rigs = models.ForeignKey(Rigs, on_delete=models.CASCADE, related_name='galleries')
file = models.ImageField(upload_to='images/')
def __str__(self):
return self.name
class Specs(models.Model):
cpu = models.CharField(max_length=50)
motherboard = models.CharField(max_length=50)
rigs = models.ForeignKey(Rigs, on_delete=models.CASCADE)
def __str__(self):
return self.cpu
I am using this library for the multi-uploading of images (https://github.com/Chive/django-multiupload), so basically I structured my Forms similar to what the document states:
class SpecsForm(forms.ModelForm):
class Meta:
model = Specs
fields = ('rigs', 'motherboard')
class RigForm(forms.ModelForm):
class Meta:
model = Rigs
fields = ('name','featured_img')
gallery_image = MultiMediaField(
min_num=1,
max_num=3,
max_file_size=1024*1024*5,
media_type='image'
)
def save(self, commit=True):
instance = super(RigForm, self).save(commit)
for each in self.cleaned_data['gallery_image']:
Gallery.objects.create(file=each, rigs=instance)
return instance
As well as my Views:
class newrig(CreateView):
model = Rigs
form_class = RigForm
template_name = 'flexrigapp/newrig.html'
success_url = '?success'
My problem is that I couldn't figure out how to include the SpecsForm on my HTML Form as well as save it on my database with the correct Foreign Key.
I have already tried transferring this one on my views with some changes but still no good.
def save(self, commit=True):
instance = super(RigForm, self).save(commit)
for each in self.cleaned_data['gallery_image']:
Gallery.objects.create(file=each, rigs=instance)
return instance
Also, I tried a function based views instead of class based but the problem is that the multi-uploading validation doesn't work on this approach of mine or maybe my code is not correct.
The expected result is that data should be saved on its respective databases (Rigs, Specs, Gallery) with the proper Foreign key on the Specs and Gallery. Right now, I can only save the Rigs and Gallery.
This is Django 2.2.4, by the way.
UPDATE
I tried updating my Views to Function-Based View just like what #dirkgroten suggested.
def newrig(request):
if request.method == 'POST':
rig_form = RigForm(request.POST, request.FILES, prefix="rigs")
specs_form = SpecsForm(request.POST, prefix="specs")
if rig_form.is_valid() and specs_form.is_valid():
rigs = rig_form.save()
specs = specs_form.save(commit=False)
specs.rigs = rigs
specs.save()
return HttpResponseRedirect("?success")
else:
rig_form = RigForm(prefix="rigs")
specs_form = SpecsForm(prefix="specs")
return render(request, "flexrigapp/newrig.html", {'rig_form': rig_form,'specs_form': specs_form,})
No data is being saved on the database now. No error logs.

Related

Django ModelChoiceField Issue

I've got the following Situation, I have a rather large legacy model (which works nonetheless well) and need one of its fields as a distinct dropdown for one of my forms:
Legacy Table:
class SummaryView(models.Model):
...
Period = models.CharField(db_column='Period', max_length=10, blank=True, null=True)
...
def __str__(self):
return self.Period
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'MC_AUT_SummaryView'
Internal Model:
class BillCycle(models.Model):
...
Name = models.CharField(max_length=100, verbose_name='Name')
Period = models.CharField(max_length=10, null=True, blank=True)
Version = models.FloatField(verbose_name='Version', default=1.0)
Type = models.CharField(max_length=100, verbose_name='Type', choices=billcycle_type_choices)
Association = models.ForeignKey(BillCycleAssociation, on_delete=models.DO_NOTHING)
...
def __str__(self):
return self.Name
Since I don't want to connect them via a Foreign Key (as the SummaryView is not managed by Django) I tried a solution which I already used quite a few times. In my forms I create a ModelChoiceField which points to my Legacy Model:
class BillcycleModelForm(forms.ModelForm):
period_tmp = forms.ModelChoiceField(queryset=SummaryView.objects.values_list('Period', flat=True).distinct(),
required=False, label='Period')
....
class Meta:
model = BillCycle
fields = ['Name', 'Type', 'Association', 'period_tmp']
And in my view I try to over-write the Period Field from my internal Model with users form input:
def billcycle_create(request, template_name='XXX'):
form = BillcycleModelForm(request.POST or None)
data = request.POST.copy()
username = request.user
print("Data:")
print(data)
if form.is_valid():
initial_obj = form.save(commit=False)
initial_obj.ModifiedBy = username
initial_obj.Period = form.cleaned_data['period_tmp']
initial_obj.Status = 'Creating...'
print("initial object:")
print(initial_obj)
form.save()
....
So far so good:
Drop Down is rendered correctly
In my print Statement in the View ("data") I see that the desired infos are there:
'Type': ['Create/Delta'], 'Association': ['CP'], 'period_tmp': ['2019-12']
Still I get a Select a valid choice. That choice is not one of the available choices. Error in the forms. Any ideas??

How to pass pk of detail view into fk of form

Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.

django manytomany field using through and formwizard

I am trying to create a pretty complicated form and break it up using formwizard. The first thing I am trying to do is get the ManyToManyField using through to display, Then I need to figure out how to make it all save.
#models.py
----------------------
class Meat(models.Model):
name = models.charField(max_length=200)
company = models.CharField(max_length = 200)
class Starch(models.Model):
name = models.CharField(max_length=200)
company = models.CharField(max_length=200)
class Recipe(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(help_text='Please describe the finished dish')
meat = models.ManyToManyField('Meat' through='RecipeMeat')
meat_notes = models.TextField()
starch = models.ManyToManyField('Starch' through='RecipeStarch')
starch_notes = models.TextField()
class RecipeMeat(models.Model):
recipe = models.ForeignKey(Recipe)
meat = models.ForeignKey(Meat)
qty = models.FloatField()
class RecipeStarch
recipe = models.ForeignKey(Recipe)
starch = models.ForeignKey(Starch)
qty = models.FloatField()
.
#forms.py
-------------------
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('name', 'description')
class RecipeMeatForm(forms.ModelForm):
class Meta:
model = RecipeMeat
class RecipeMeatNotesForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('meat_notes',)
class RecipeStarch(forms.ModelForm):
class Meta:
model = RecipeStarch
class RecipeStarchNotesForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('starch_notes')
MeatFormSet = inlineformset_factory(Recipe, RecipeMeat, form=RecipeMeatForm, extra=1)
.
#views.py
---------------------------
class CreateRecipeWizard(SessionWizardView):
template_name = "create-recipe.html"
instance = None
file_storage = FileSystemStorage(location= 'images')
def dispatch(self, request, *args, **kwargs):
self.instance = Recipe()
return super(CreateRecipeWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance( self, step ):
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect(reverse(all-recipes))
.
#urls.py
------------------------------
url(r'^create-recipe/$', views.CreateRecipeWizard.as_view([RecipeForm, MeatFormSet, RecipeMeatNotesForm, RecipeStarchNotesForm]), name='create-recipe'),
.
I am a bit of a rookie with this django stuff. The Recipe part is much longer and more complicated but pretty much the same pattern. If any one could help point me in the right on how to get my ManyToManyField using through part figured out or pointed in the right direction it would be greatly appreciated.
To save the ManyToMany relationship on a formwizard process you can do something like this;
def done(self, form_list, **kwargs):
form_data_dict = self.get_all_cleaned_data()
m2mfield = form_data_dict.pop('m2mfield')
instance = form_list[0].save()
for something in m2mfield:
instance.m2mfield.add(something)
return render_to_response(
'done.html', {},
context_instance=RequestContext(self.request)
)
In this example the first form in the list is a ModelForm for the thing I'm trying to create and it has a ManyToManyField to another model for which I have a form second in the process. So I grab that first form & save it, then grab the field from the cleaned data from the second form and save the selected options to the M2M field.

Django CBV Forms prepopulated foreign key dataset

I'm trying to use CBVs as much as possible and want to pre-populate data in a ModelForm based on a generic.CreateView with some data passed in via URL.
I might be over thinking or confusing myself. All code abridged for legibility
We have an inventory system with PartNumbers (abstractions), Carriers (actual instances of PartNumbers with location, serial and quantity numbers) and Movements for recording when items are extracted from the inventory, how much is taken and what Carrier it came from.
I would like to have the "extract inventory" link on the PartNumber detail page, and then have the available carriers ( pn.carrier_set.all() ) auto filled into the FK drop down on the MovementForm.
models.py
class PartNumber(models.Model):
name = models.CharField("Description", max_length=100)
supplier_part_number = models.CharField(max_length=30, unique=True)
slug = models.SlugField(max_length=40, unique=True)
class Carrier(models.Model):
part_numbers = models.ForeignKey(PartNumber)
slug = models.SlugField(max_length=10, unique=True, blank=True, editable=False)
location = models.ForeignKey(Location)
serial_number = models.CharField(max_length=45, unique=True, null=True, blank=True)
qty_at_new = models.IntegerField()
qty_current = models.IntegerField()
class Movement(models.Model):
carrier = models.ForeignKey(Carrier)
date = models.DateField(default=timezone.now())
qty = models.IntegerField()
I have been playing around with get_initial() and get_form_kwargs() without success:
In urls.py I collect the PartNumber via url as pn_slug
url(r'^partnumber/(?P<pn_slug>[-\w]+)/extract/$', views.MovementCreate.as_view(), name='pn_extract'),
forms.py is generic
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
views.py
class MovementCreate(generic.CreateView):
form_class = MovementForm
model = Movement
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.request.POST.get("pn_slug")
return kwargs
# here we get the appropriate part and carrier and.
# return it in the form
def get_initial(self):
initial = super(MovementCreate, self).get_initial()
# this didn't work, hence using get_form_kwargs
#pn = PartNumber.objects.get(slug=self.request.POST.get("pn_slug"))
pn = PartNumber.objects.get(slug=self[pn_slug])
carriers = pn.carrier_set.all()
initial['carrier'] = carriers
return initial
As it stands, I'm getting "global name 'pn_slug' is not defined" errors - but I doubt that error accurately reflects what I have done wrong.
I have been using these posts as rough guidelines:
How to subclass django's generic CreateView with initial data?
How do I use CreateView with a ModelForm
If I understand you correctly from our comments, all you need is just to change the queryset of the MovementForm's carrier field to set the available options. In that case, I would use get_initial nor get_form_kwargs at all. Instead, I would do it in get_form:
def get_form(self, *args, **kwargs):
form = super(MovementCreate, self).get_form(*args, **kwargs)
pn = PartNumber.objects.get(slug=self.kwargs['pn_slug'])
carriers = pn.carrier_set.all()
form.fields['carrier'].queryset = carriers
return form
Another way to do it would be to use get_form_kwargs:
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.kwargs.get("pn_slug")
return kwargs
Then, in the form's __init__, set the queryset:
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
def __init__(self, *args, **kwargs):
pn_slug = kwargs.pop('pn_slug')
super(MovementForm, self).__init__(*args, **kwargs)
pn = PartNumber.objects.get(slug=pn_slug)
carriers = pn.carrier_set.all()
self.fields['carrier'].queryset = carriers
Personally, I would prefer the first method as it is less code.

Django 1.5 ModelForm like admin in view with images and foreign key

I have the following models:
class Quiver(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
is_default = models.BooleanField(default=False)
type = models.CharField(max_length=1, choices=QUIVER_TYPES)
category = models.CharField(max_length=255, choices=QUIVER_CATEGORIES)
def __unicode__(self):
return u'[%s] %s %s quiver' % (
self.user.username,
self.get_type_display(),
self.get_category_display())
class Image(models.Model):
photo = models.ImageField(upload_to=get_upload_file_path)
is_cover = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
def save(self, *args, **kwargs):
try:
this = Image.objects.get(pk=self.pk)
if this.photo != self.photo:
this.photo.delete(save=False)
except Image.DoesNotExist:
pass
super(Image, self).save(*args, **kwargs)
class Surfboard(models.Model):
quiver = models.ForeignKey(Quiver)
brand = models.CharField(max_length=255)
model = models.CharField(max_length=255)
length = models.CharField(max_length=255)
width = models.CharField(max_length=255, blank=True)
thickness = models.CharField(max_length=255, blank=True)
volume = models.CharField(max_length=255, blank=True)
images = generic.GenericRelation(Image)
def __unicode__(self):
return u'%s %s %s' % (self.length, self.brand, self.model)
def get_cover_image(self):
"Returns the cover image from the images uploaded or a default one"
for image in self.images.all():
if image.is_cover:
return image
return None
I'd like to be able to have the same form I have in the admin in my frontend view /surfboard/add:
As a new Django fan and user, I started to create the form from scratch. Not being able to do what I want with including the foreign key "quiver" as a dropdown list, I found in the doc the ModelForm, and decided to use it, so here what I got:
class SurfboardForm(ModelForm):
class Meta:
model = Surfboard
In my view, it looks like this and it's already a good start:
So now, I wanted to have a way to add pictures at the same time, and they are linked to a surfboard via a Generic Relation. Here I don't find the way to do a implementation like in the admin, and get frustrated. Any tips to do so?
Thanks!
What you seek is called an inline formset - see the docs for more.
It's also handy that you can render a formset quickly with {{ formset.as_p }}, but you'll need to write some JavaScript (or use the JavaScript that's used in the Django admin) to handle adding and removing forms.