Django: Edit ModelForm using AutoField - python

I'm trying to make a view where the user can edit DB records through a form in a template. I've searched a lot of web pages (and Django docs as well) where they teach how to make these views, but they always use the "id" that Django generates for each Model. In this particular Model, I have to use an AutoField to override the "id". Is there a way to use this AutoField as an "id" of the record with Django?
Here's my complete model:
class T031003 (models.Model):
C003IDCD = AutoField(primary_key=True)
C003INST = models.IntegerField(unique=True) #usar AutoSlug
C003TPCD = models.CharField(max_length=1)
C003CHCD = models.CharField(max_length=14)
C003MTR = models.CharField(max_length=30, blank=True, null=True)
C003CTCD = models.CharField(max_length=3)
C003RZSC = models.CharField(max_length=60, blank=True, null=True)
C003EML = models.EmailField(max_length = 254, blank=True, null=True)
C003LOGA = models.CharField(max_length=20)
C003LOGB = models.DateTimeField()
C003LOGD = models.CharField(max_length=15, blank=True, null=True)
C003LOGF = models.CharField(max_length=20, blank=True, null=True)
def __unicode__(self):
return '%s' % self.C003MTR
class T031003Form(ModelForm):
class Meta:
model = T031003
ordering = ["-C003MTR"]
exclude = ('C003LOGA','C003LOGB','C003LOGD','C003LOGE','C003LOGF')
And here's the view I tried to do, but it gives me the error "No T031003 matches the given query." and it's right, since there is no "id" in the table:
def t031003form_edit(request, id=None):
pin = get_object_or_404(T031003, pk=id)
form = T031003Form(request.POST or None, instance=pin)
if request.method == 'POST':
if form.is_valid():
form = form.save(False)
form.C003LOGA = request.user
form.C003LOGB = datetime.date.today()
form.C003LOGD = request.META['REMOTE_ADDR']
form.C003LOGF = request.META['USERDOMAIN']
form.save()
form = T031003Form()
else:
return HttpResponseRedirect('/erro/')
return render_to_response('T031003Form_edit.html', {'form': form,}, context_instance=RequestContext(request))
Any help would be very appreciated!

If a model has an AutoField — an auto-incrementing primary key — then that auto-incremented value will be calculated and saved as an attribute on your object the first time you call save():
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id # Returns None, because b doesn't have an ID yet.
>>> b2.save()
>>> b2.id # Returns the ID of your new object.
There's no way to tell what the value of an ID will be before you call save(), because that value is calculated by your database, not by Django.
ref : https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs

Well, thanks to the help from a close friend, I could do the trick using formsets. Here's the view:
def t031002form_edit(request, id_auto):
j = get_object_or_404(T031002, pk=id_auto)
T031003FormSet = modelformset_factory(T031002, can_delete=True, max_num=1)
if request.method == 'POST':
form = T031002FormSet(request.POST or None, request.FILES or None, queryset=T031002.objects.filter(pk=id_auto))
if form.is_valid():
instance = form.save(commit=False)
form.C003LOGA = request.user
form.C003LOGB = datetime.date.today()
form.C003LOGD = request.META['REMOTE_ADDR']
form.C003LOGF = request.META['USERDOMAIN']
for reform in instance:
reform.save()
else:
return HttpResponseRedirect('/erro/')
else:
form = T031002FormSet(queryset=T031002.objects.filter(pk=id_auto))
return render_to_response(('T031002Form_edit.html'), {'form': form,}, context_instance=RequestContext(request))
So, with formsets, you can work nicely and with no worries. Hope it helps others with this same questioning.

Related

Failure to save certain attributes from ModelForm to django database (Logic error)

I have a ModelForm called ListingForm. It takes data from a user but I have stopped some of the model attributes from appearing in this form as I want to feed data to those myself. I have put print statements in my createlisting function in views.py to inspect if the data is actually being saved correctltly, it turns out the data is being saved. Here is the createlisting function:
def create_listing(request):
if request.method == 'POST':
import datetime
listing_form = ListingForm(request.POST, request.FILES)
if listing_form.is_valid():
bid = listing_form.cleaned_data['starting_bid']
print(bid)
listing_form.save(commit=False)
listing_form.user = request.user
print(listing_form.user)
listing_form.date_made = datetime.datetime.today()
listing_form.is_active = True
listing_form.category = Category.objects.get(name=listing_form.cleaned_data['listing_category'])
print(listing_form.category)
#The form is being saved correctly here, and the print statements give the correct results in my terminal
listing_form.save()
Bid.objects.create(user= request.user, value=bid, listing=listing_form.instance)
all_listings = Listing.objects.all()
return render(request, 'auctions/index.html', {
'all_listings': all_listings })
else:
listing_form = ListingForm()
return render(request, 'auctions/createlisting.html',{
'listing_form':listing_form
})
However, when I try to access the data from the model Listing from which the ListingForm is inheriting, the print statements I have put for debugging return the default values for certain fields (category and user) instead of the values I have saved in the ListingForm.
Here is the code that allows me to view the data for the model instance I have created. Mind you, all the other fields have saved correctly except for the fields category and user:
def view_listing(request, listing_id):
listing = Listing.objects.get(pk=listing_id)
#the print results return the default values for the fields category and user instead of the values I saved in my ModelForm
print(listing.category)
print(listing.user)
if request.user == listing.user:
return render(request, 'auctions/view_listing.html', {
'listing': listing,
'flag':True,
'count': listing.bids.all().count()
})
else:
return render(request, 'auctions/view_listing.html',{
'listing':listing,
'count': listing.bids.all().count()
})
What could be the problem with my code?
Also, let me provide the code for some of my models and a form as the error might be embedded in those:
Listing Model:
class Listing(models.Model):
NAME_CHOICES = [
('Fashion', 'Fashion'),
('Toys','Toys'),
('Electronics','Electronics'),
('Home', 'Home'),
('Other', 'Other')
]
title = models.CharField(max_length= 64)
date_made = models.DateTimeField(auto_now_add=True)
description = models.TextField()
user = models.ForeignKey(User, to_field='username', on_delete=models.CASCADE, related_name='user_listings', null=True)
starting_bid = models.DecimalField(decimal_places=2, max_digits=264, default=10.00)
upload_image = models.ImageField(blank=True, upload_to='media/media')
category = models.ForeignKey(Category, on_delete=models.CASCADE, to_field='name', related_name='category_listings', default=NAME_CHOICES[4][0], db_constraint=False)
listing_category = models.CharField(max_length=12, choices=NAME_CHOICES, null=True, default=NAME_CHOICES[4][0])
is_active = models.BooleanField(default=True)
watchlist = models.ForeignKey('Watchlist', on_delete=models.DO_NOTHING, related_name='listings', null=True)
Category Model:
class Category(models.Model):
NAME_CHOICES = [
('Fashion', 'Fashion'),
('Toys','Toys'),
('Electronics','Electronics'),
('Home', 'Home'),
('Other', 'Other')
]
name = models.CharField(max_length=12, choices= NAME_CHOICES, unique=True)
User Model:
class User(AbstractUser):
def __str__(self):
return f'{self.username} '
ListingForm`` (ModelForm```):
class ListingForm(ModelForm):
class Meta:
model = Listing
exclude = [
'date_made',
'user',
'category',
'is_active',
'watchlist'
]
Any form of help would be greatly appreciated.
When you call listing_form.save(commit=False) it returns an unsaved model instance with the submitted values. If you assign that to a variable, you can use it to set the other field values and save:
def create_listing(request):
if request.method == 'POST':
import datetime
listing_form = ListingForm(request.POST, request.FILES)
if listing_form.is_valid():
bid = listing_form.cleaned_data['starting_bid']
listing = listing_form.save(commit=False)
listing.user = request.user
listing.date_made = datetime.datetime.today()
listing.is_active = True
listing.category = Category.objects.get(name=listing_form.cleaned_data['listing_category'])
listing.save()
Bid.objects.create(user=request.user, value=bid, listing=listing)
# You should probably use HttpResponseRedirect to an `all_listings` page, rather than displaying them here
all_listings = Listing.objects.all()
return render(request, 'auctions/index.html', {
'all_listings': all_listings })
Here's a link to the ModelForm.save() docs.

Django is not populating correctly an specific form using a Queryset

I have created two models Leads and Deals, and I have coded some logic such that if you click a button the Lead becomes a Deal, so what I want it is that a new form is presented to the user but that form already contains the information from the Leads model.
#login_required
def close_lead(request):
if request.method == 'POST':
deal_form = DealForm(request.POST)
if deal_form.is_valid():
deal_form.save()
messages.success(request, 'You have successfully updated the status from open to Close')
id = request.GET.get('project_id', '')
obj = Leads.objects.get(project_id=id)
obj.status = "Closed"
obj.save(update_fields=['status'])
return HttpResponseRedirect(reverse('dashboard'))
else:
messages.error(request, 'Error updating your Form')
else:
id = request.GET.get('project_id', '')
obj = get_object_or_404(Leads, project_id=id)
print(obj.expected_revenue)
form = NewDealForm(request.POST or None, instance=obj)
return render(request,
"account/close_lead.html",
{'form':form})
I have done some debug and printed to the console the queryset and the information is fine, so the queryset is no the problem, the problem is that the NewForm doesn't prepopulate the new values.
models.py (only 2 models shown)
class Leads(models.Model):
CHOICES = (
('Illumination Studies','Illumination Studies'),
('Training','Training'),
('Survey Design','Survey Design'),
('Software License','Software License')
)
STATUS = (('Open','Open'),
('Closed','Closed'),
('Canceled', 'Canceled')
)
project_id = models.BigAutoField(primary_key=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
agent = models.ForeignKey(Profile, on_delete=models.CASCADE, default="agent")
created_at = models.DateTimeField(auto_now_add=True)
point_of_contact = models.ForeignKey(Client, on_delete=models.CASCADE)
expected_revenue = MoneyField(max_digits=14, decimal_places=2, default_currency='USD')
expected_licenses = models.IntegerField(blank=True)
country = CountryField(blank_label='(select country)')
status = models.CharField(max_length=10,choices=STATUS)
estimated_closing_date = models.DateField(blank=True)
services = models.CharField(max_length=20,choices=CHOICES)
def __str__(self):
return f'{self.company}'
class Deal(models.Model):
project_id = models.ForeignKey(Leads, on_delete=models.CASCADE, default='id')
agent = models.ForeignKey(Profile, on_delete=models.CASCADE, default="agent")
service = models.ForeignKey(Leads, on_delete=models.CASCADE, related_name='service')
closing_date = models.DateField(auto_now_add=True)
client = models.ForeignKey(Client, on_delete=models.CASCADE,default='client')
licenses = models.ForeignKey(Leads,on_delete=models.CASCADE, related_name='licenses')
revenue = MoneyField(max_digits=14, decimal_places=2, default_currency='USD')
comments = models.TextField(blank=True,null=True)
Now, it could be that I have to inherit from a different form?
forms.py (only NewDealForm)
class NewDealForm(forms.ModelForm):
class Meta:
model = Deal
fields = ['agent','client','project_id','service', 'licenses','revenue', 'comments']
Obviously, worst-case scenario is to create a dictionary to extract the data from the queryset and then pass it to the form, but I'm sure Django has a more elegant way to handle this process.
Well, I guess sometimes Stack Overflow pushes you to solve your own issues, this is the solution.
Essentially, the initial=queryset value was not initializing the form mainly because I have very specific relationships in my model, so what I did is to create a dictionary (key:value) with the form field as key, and my queryset from my model as the value, the code is as below:
'''
def close_lead(request):
if request.method == 'POST':
deal_form = DealForm(request.POST)
if deal_form.is_valid():
deal_form.save()
messages.success(request, 'You have successfully updated the status from open to Close')
id = request.GET.get('project_id', '')
obj = Leads.objects.get(project_id=id)
obj.status = "Closed"
obj.save(update_fields=['status'])
return HttpResponseRedirect(reverse('dashboard'))
else:
messages.error(request, 'Error updating your Form')
else:
id = request.GET.get('project_id', '')
obj = get_object_or_404(Leads, project_id=id)
m = obj.__dict__
keys = Leads.objects.get(project_id=m['project_id'])
form_dict = {'project_id':keys.project_id,
'agent':keys.agent,
'client':keys.point_of_contact,
'company':keys.company,
'service':keys.services
}
form = NewDealForm(request.POST or None,initial = form_dict)
return render(request,
"account/close_lead.html",
{'form':form})
'''
As you can see, I create an object dictionary because the forms are different, so they share some common values not all, and then I simply adapt the dictionary, nice and easy, but I somehow expected that Django somehow finds relationships by name?, but maybe the batteries are not included for this.

Django related model not updating on form submission

How do I update a non existing related object through Django model forms ?
I have two objects: Participant and Emergency. Emergency is a child of participant like if run the query: participant = ParticipantInfo.objects.get(pk = prk) I can access emergency = participant.emergency.
I cannot update emergency with data from a form using a POST request.
Can anyone help me please.
Thanks
Here's my models.py for clarity.
models.py
class EmergencyInfo(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
phone_number = models.CharField(max_length=50)
email = models.EmailField(max_length=100, blank=True, verbose_name="Emergency Contact Email")
relationship = models.CharField(max_length=100)
class ParticipantInfo(models.Model):
first_name = models.CharField(max_length=100)
middle_initial = models.CharField(max_length=1, blank=True)
emergency = models.ForeignKey(EmergencyInfo, on_delete = models.CASCADE, editable= False, null=True, blank=True)
views.py
def update_participant(request, pk):
# this function comes after update_specific
if request.method == "GET":
forms = get_participant_form_data(pk)
context = {'forms': forms, 'pk': pk}
return render(request, 'core/participantinfo_update_form.html', context)
if request.method == "POST":
return update_specific_form(request, pk)
def update_specific_form(request, pk):
participant = ParticipantInfo.objects.get(pk = pk)
# if the object didn't exist create it like normal
if participant.emergency is None:
emergencyform =EmergencyForm(request.POST)
if (emergencyform.is_valid):
emergencyform.save()
messages.success(request, 'saved')
return redirect(request.path_info)
# if the object exists, update it
if participant.emergency is not None:
emergencyform = EmergencyForm(request.POST, instance = participant.emergency)
if (emergencyform.is_valid):
emergencyform.save()
messages.success(request, 'saved')
return redirect(request.path_info)
Your problem seems to be with the is_valid method. Replace it with is_valid(). So your lines would be:
if (emergencyform.is_valid()):
#code
I have found an answer. Not only I needed to call the is_valid() instead of is_valid
but I also needed to tie the created object with it's parent like:
Save the new child object
Save the parent objects (updates the null
foreign key to a value)
*#get the existing parent object*
participant = ParticipantInfo.objects.get(pk = pk)
*#if no child object exists*
if participant.emergency is None:
emergencyform =EmergencyForm(request.POST)
if (emergencyform.is_valid()):
emergency = emergencyform.save(commit=False)
participant.emergency = emergency
emergencyform.save()
participant.save()
messages.success(request, 'saved')
return redirect(request.path_info)

Django - Trying to pass an instance of a model field to a form field

I'm going to do my best not to sound like a real dummy, but no promises. I am a paramedic and I'm trying to make an app to document unit checks electronically.I have a model field that is foreign keyed to a few other models in my project. This field designates the unit the user is staffing for that day. I want the user to choose the unit he/she is staffing that day and have that information auto-fill any forms filled out for that session. I've tried storing the object using sessions and I get this "Object of type 'MedicUnit' is not JSON serializable". I've used the model_to_dict method and tried to pass the string of the unit name through the form_valid method but I get this "Cannot assign "'Medic 2'": "DailyCheck.medic_unit_number" must be a "MedicUnit" instance." I'm relatively new to programming and Django and this seems like a very easy problem to fix, but my google searching skills are not getting me anywhere. Here is my code:
Model.py for the origin of the unit_name model field
class MedicUnit(models.Model):
unit_name = models.CharField(max_length=50, default='')
is_active = models.BooleanField(default=True)
def __str__(self):
return self.unit_name
Model.py for one of the foreign key references to the unit_name
class DailyCheck(models.Model):
daily_user = models.ForeignKey(User, on_delete=models.PROTECT)
record_date = models.DateTimeField(auto_now=True)
medic_unit_number = models.ForeignKey('components.MedicUnit', related_name='medic_unit_number', on_delete=models.PROTECT, default='')
unit_property_number = models.ForeignKey('components.Vehicle', related_name='unit_property_number', on_delete=models.PROTECT, default='')
mileage = models.IntegerField(default=0)
narc_seal_number = models.IntegerField(default=0)
emergency_lights = models.BooleanField()
driving_lights = models.BooleanField()
red_bag = models.BooleanField()
LP_15 = models.BooleanField()
BLS_bag = models.BooleanField()
RTF_bag = models.BooleanField()
suction = models.BooleanField()
oxygen = models.BooleanField()
free_text = models.TextField(default='')
views.py for the directly above model
def check_home_view(request):
if request.method == 'POST':
form = ChooseMedicUnit(request.POST or None)
if form.is_valid():
unit_name = form.cleaned_data.get('medic_unit_number')
request.session['unit_name'] = model_to_dict(unit_name)
print(request.session['unit_name'])
return redirect('daily')
else:
form = ChooseMedicUnit()
return render(request, 'checks/checks_home.html', {'form':form})
class checkAdd(CreateView):
model = DailyCheck
fields = ['unit_property_number', 'mileage', 'narc_seal_number', 'emergency_lights', 'driving_lights', 'red_bag', 'LP_15', 'BLS_bag', 'RTF_bag', 'suction', 'oxygen', 'free_text']
success_url = '/checks'
def form_valid(self, form):
form.instance.daily_user = self.request.user
form.instance.medic_unit_number = self.request.session['unit_name']['unit_name']
return super().form_valid(form)
forms.py
class ChooseMedicUnit(forms.ModelForm):
class Meta:
model = DailyCheck
fields = ['medic_unit_number']
I think you can use MedicUnit.id. This should be sufficient to resolve the issue of initializing the field from the session in other forms:
def check_home_view(request):
if request.method == 'POST':
form = ChooseMedicUnit(request.POST or None)
if form.is_valid():
request.session['unit_name'] = form.cleaned_data.get('medic_unit_number').id # see here
print(request.session['unit_name'])
return redirect('daily')
else:
form = ChooseMedicUnit()
return render(request, 'checks/checks_home.html', {'form':form})
Thank you so much for the answer Andrey. I will try that too. I found that all I had to do was import the MedicUnit model to my view and change my form_valid method to the following:
def form_valid(self, form):
form.instance.daily_user = self.request.user
form.instance.medic_unit_number = MedicUnit.ojbects.get(pk=self.request.session['unit_name']['id'])
return super().form_valid(form)
Apparently sessions cannot store objects since after Django 1.5 I think. Someone may have to fact check me on that. So I referenced an instance of the object with a dictionary value from the model_to_dict data stored in the session with the MedicUnit.object.get call.
If you are having the same problem, you can print the session info to the terminal with a print statement just like in my check_home_view function view. I used that info to see what key was necessary to call primary key number.
I will check Andrey's solution later today and see how well that works. It seems a bit cleaner than my solution.

Django User foreign key in View vs in model.save() method

I have the following model (simplified):
class Candidate(models.Model):
""" Model for candidate clients """
# fields
general_category = models.ForeignKey('GeneralCategory',
related_name='candidate',
null=True,
blank=True,
# default=1,
verbose_name='Γενική Κατηγορία',)
brand_name = models.CharField(max_length=160,
blank=True,
verbose_name='Επωνυμία')
creation_date = models.DateTimeField(null=True, blank=True, verbose_name='Πρώτη καταχώρηση')
last_edited = models.DateTimeField(null=True, blank=True, verbose_name='Τελευταία επεξεργασία')
first_edited_by = models.ForeignKey(User,
related_name='first_edited_candidates',
blank=True,
null=True,
verbose_name='Πρώτη επεξεργασία από',)
last_edited_by = models.ForeignKey(User,
related_name='last_edited_candidates',
blank=True,
null=True,
verbose_name='Τελευταία επεξεργασία από',)
def save(self, *args, **kwargs):
""" On save, update timestamps and user fields """
if 'request' in kwargs:
request = kwargs.pop('request')
else:
request = None
if not self.id:
self.creation_date = timezone.now()
self.last_edited = timezone.now()
if request is not None:
if not self.first_edited_by:
self.first_edited_by = request.user
self.last_edited_by = request.user
log.info(self)
return super(Candidate, self).save(*args, **kwargs)
def __str__(self):
return self.brand_name + '[' + str(self.__dict__) + ']'
If I fire up the debugger in PyCharm I can see that the two User foreign keys are populated as expected in my detail view, but inside the model.save() method they are None. The other foreign key (general_category) is populated as expected.
Why is that? Does it have something to do with the self keyword?
My view (again, simplified) is this:
#login_required
#require_http_methods(["GET", "POST"])
def candidate_detail(request, candidate_id):
candidate = get_object_or_404(Candidate, pk=candidate_id)
original_http_referrer = request.GET.get('next')
if request.method == 'GET':
form = CandidateForm(instance=candidate)
elif request.method == 'POST':
form = CandidateForm(request.POST, instance=candidate)
if form.is_valid():
candidate.save(request=request)
return HttpResponseRedirect(original_http_referrer)
# else:
# TODO: show some error message ?
context = {'candidate': candidate,
'form': form,
'original_http_referrer': original_http_referrer}
return render(request, 'candidates/candidate_detail.html', context)
I'm using Django 1.8 with Python 3.4.
UPDATE: It seems that the value of the foreign keys is lost in the line
form = CandidateForm(request.POST, instance=candidate)
The weird thing is that, if I step-in and go line-by-line with the debugger, my program ends up working as expected! (I have also tried this using manage.py runserver to make sure it is not a bug in the PyCharm's server implementation and it's not)
I'll try logging my model at each step tomorrow to narrow down the offending code. Just to make sure, here is my form's code (not simplified):
from django.forms import ModelForm
from candidates.models import Candidate
class CandidateForm(ModelForm):
class Meta:
model = Candidate
fields = '__all__'
You didn't save the form.
if form.is_valid():
candidate = form.save(commit=False)
candidate.save(request=request)
Note that the first four lines of the save method can be simplified to one:
request = kwargs.pop('request', None)

Categories