I have been trying to teach myself something new this week by following various tutorials on Google APIs. I am hitting a wall on the form saving bit, so helping you can help.
Basically, I am trying to offer user the possibility to enter their first line of address for Google to then submit suggestions of addresses.
This first bit works (when I enter the first line of an address, I get generated a list of suggestions).
However, when I am trying to save the form, this doesn't seem to be saved anywhere. And I am not getting any error message either (if anything I am getting the a "success message" I am supposed to receive when the form is successfully submit).
I thought there might be a field that is not getting being populated in the html. So I tried to empty one and tried to save. But on this occasion I received an error message, saying the field is missing. Which makes me thing this hasnt anything to do with the fields in the form but probably how I save my form in the views.py.
I must admit I am way out of my comfort zone here (this ajax thing proved to be difficult to make work, and all I had to do was to follow a tutorial..), so I wouldnt be surprised I am not capturing all the code needed for you to help. If so let me know what you need to see.
Views.py
def is_ajax(request):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
def profile(request):
result = "Error"
message = "There was an error, please try again"
form = profileform(instance = request.user)
Profile.objects.get_or_create(user=request.user)
if is_ajax(request=request):
form = profileform(data = request.POST, instance = request.user)
if form.is_valid():
obj = form.save()
obj.has_profile = True
obj.save()
result = "Success"
message = "Your profile has been updated"
else:
message = FormErrors(form)
data = {'result': result, 'message': message}
return JsonResponse(data)
else:
context = {'form': form}
context['google_api_key'] = settings.GOOGLE_API_KEY
context['base_country'] = settings.BASE_COUNTRY
return render(request, 'main/profile.html', context)
Froms.py
from .models import Profile
class ProfileForm(forms.ModelForm):
address = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
town = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
county = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
post_code = forms.CharField(max_length=8, required=True, widget = forms.HiddenInput())
country = forms.CharField(max_length=40, required=True, widget = forms.HiddenInput())
longitude = forms.CharField(max_length=50, required=True, widget = forms.HiddenInput())
latitude = forms.CharField(max_length=50, required=True, widget = forms.HiddenInput())
class Meta:
model = Profile
fields = ['address','town','county','post_code','country','longitude','latitude']
Models.py
class Profile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
address = models.CharField(verbose_name="Address",max_length=100, null=True, blank=True)
town = models.CharField(verbose_name="Town",max_length=100, null=True, blank=True)
county = models.CharField(verbose_name="County",max_length=100, null=True, blank=True)
post_code = models.CharField(verbose_name="Post Code",max_length=8, null=True, blank=True)
country = models.CharField(verbose_name="Country",max_length=100, null=True, blank=True)
longitude = models.CharField(verbose_name="Longitude",max_length=50, null=True, blank=True)
latitude = models.CharField(verbose_name="Latitude",max_length=50, null=True, blank=True)
captcha_score = models.FloatField(default = 0.0)
has_profile = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
def __str__(self):
return str(self.user)
Interesting that you're getting to your success message. Your issue might be that you're passing a user instance to a form that expects a profile model (not sure why this doesn't throw an error though):
# capture the profile that is created with get_or_create
profile, created = Profile.objects.get_or_create(user=request.user)
if is_ajax(request=request):
form = profileform(data = request.POST, instance=profile) # change instance
Instead of
else:
message = FormErrors(form)
Try this
else:
message = form.errors
Related
I am building an web app that allows users to request items.
The user will select the item, the location, the start date and end date of the borrowing in a form.
I have the following model which I created the form from:
class Request(models.Model):
shootkit = models.ForeignKey(Shootkit, on_delete=models.CASCADE, null=True, blank=True)
username = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
request_start_date = models.DateField('Start Date')
request_end_date = models.DateField('End Date')
location = models.ForeignKey(Location, on_delete=models.CASCADE)
I have 5 Items in the database.
And here's the model:
class Shootkit(models.Model):
shootkit = models.CharField('Shootkit', max_length=100, null=True, blank=True)
And the def in views:
def request_form(request):
submitted = False
if request.method == "POST":
form = requestForm(request.POST)
if form.is_valid():
stock = form.save(commit=False)
stock.username = request.user
stock.save()
return HttpResponseRedirect('/request_form?submitted=True')
else:
form = requestForm(initial = {'username': request.user})
if 'submitted' in request.GET:
submitted = True
return render(request, 'request_form.html', {'form':form, 'submitted':submitted,})
The issue here is that I want to grey out (disable?) the items options which have been already requested.
so I know that I can filter the date greater or equal than today in objects in the Request table so it means that the item still unavailable. But I am not sure how to block/disable the item.
Can anyone help please?
(EDIT)
Apologies if my question is not clear.
Please see requestForm:
class requestForm(ModelForm):
class Meta:
model = Request
fields ='shootkit', 'location', 'request_start_date', 'request_end_date',
widgets = {
'request_start_date':DateInput(attrs={'class':'form-control'}),
'request_end_date':DateInput(attrs={'class':'form-control'}),
'shootkit': forms.Select(attrs={'class':'form-select'}),
#'username': forms.TextInput(attrs={'class':'form-control'}),
'location': forms.Select(attrs={'class':'form-select'})
}
and here is a pic of the request_form.html
my intention is that if any of those shootkits ( for example shootkit #2 and shootkit #4 have been requested and the end date of the borrowing is next week for example. Then the shootkits should appear as disabled in the form like this
I hope that makes more sense.
Thank you.
File "C:\Users\NICSI\Desktop\lastnow\mynew\cheque\models.py", line 39, in st
r
return (self.related.relation.username).title()
AttributeError: 'NoneType' object has no attribute 'relation'
[28/Sep/2018 12:36:49] "GET /admin/cheque/mycheque/ HTTP/1.1" 500 301999
Exception Value:
Signs matching query does not exist.
DJANGO throw these two errors how could i resolve this to show the relation between user and these details
models.py
class Signs(models.Model):
relation = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(null=True, blank=True, max_length=1000, help_text="User Department")
mobile = models.CharField(null=True, blank=True, max_length=1000, help_text="User Mobile")
def __str__(self):
return (self.relation.username).title()
class Meta:
verbose_name_plural = "Registration Information"
class Mycheque(models.Model):
related = models.ForeignKey(Signs, on_delete=models.CASCADE, null=True, blank=True)
to_pay = models.CharField(max_length=250, null=True, blank=True)
amount = models.BigIntegerField(default=0, null=True, blank=True)
amount_in_words = models.CharField(max_length=10000, null=True, blank=True)
vouchar_no = models.BigIntegerField(default=0, null=True, blank=True)
dated = models.DateTimeField(auto_now_add=True, null=True, blank=True)
cheque_no = models.BigIntegerField(default=0, null=True, blank=True)
cheque_date = models.CharField(max_length=10, null=True, blank=True)
account_no = models.BigIntegerField(default=0, null=True, blank=True)
def save(self):
self.dated = datetime.now()
super(Mycheque, self).save()
def __str__(self):
return (self.related.relation.username).title()
class Meta:
verbose_name_plural = "Single Cheque Of Users"
views.py
def mycheque(request):
if request.method == "POST":
userdata = User.objects.get(username = request.user)
user_data = Signs.objects.get(relation_id=userdata.id)
if userdata.check_password(passwd) == True:
to_p = request.POST['topay']
amnt = request.POST['amount1']
amnt_in_words = request.POST['amount_string']
vouch_no = request.POST['voucharno']
d = request.POST['date']
cheq_no = request.POST['chequeno']
cheq_date = request.POST['chequedate']
acc_no = request.POST['accountno']
single = Mycheque(to_pay=to_p, amount=amnt, amount_in_words=amnt_in_words, vouchar_no=vouch_no, dated=d, cheque_no=cheq_no, cheque_date=cheq_date, account_no=acc_no)
single.save()
messages.success(request, "Your Cheque is created")
else:
messages.error(request, "Please Try again...")
return render(request, 'cheque/mycheque.html', {})
AttributeError
The error originates from the fact that the Mycheque.related relation is NULLable: it is possible to set it to NULL.
This means that for some Mycheques, the self.related object will be None, and therefore self.related.relation will error.
You will thus to add some logic to the __str__ to handle that case, for example:
def __str__(self):
if self.related:
return self.related.relation.username.title()
else:
return 'no related!'
That being said, I find the __str__ function rather "weird": the textual representation of a Mycheque only takes the related.relation object into account? So you should consider redesigning this.
The same "scanario" can happen on multiple places, so you should run a search and fix it accordingly
Singns does not exists
This is probably due to the line:
user_data = Signs.objects.get(relation_id=userdata.id)
The strange thing is here that you do nothing with the user_data variable at all, so - given you implemented the view correctly - can remove the line.
That being said the line itself makes no sense: you query a ForeignKey in reverse, that means that for a User object, there can be zero, one, or multiple Signs objects. So it makes more sense to use .filter(..) since if there are no related Signs (or multiple) this query will error.
Refactoring the view, and introducing a ModelForm
The line above:
userdata = User.objects.get(username = request.user)
makes no sense either: the request.user is a User object, so it is equivalent to:
userdata = request.user
Other piculiarities in the view are that you perform authentication yourself, instead of using the #login_required [Django-doc], and furthermore it here definitely makes sense to define a ModelForm [Django-doc], by using a form, the view will probably be reduced to 5-7 lines, and the errors are handled at the correct place.
I created a custom user model called Agent by extending AbstractUser. Now for some reason, my signup page is stuck and I can't figure out why (it was working fine before I created the custom user). When I click the Sign Up button, the page is stuck on Waiting for localhost...
There are 2 additional models on top of Agent that are created during registration - AgentBasicInfo and AgentPremiumInfo. AgentBasicInfo is displayed on the sign up page, while AgentPremiumInfo is created in the background, and not actually displayed during registration.
When I check my admin page, I see that an Agent model has been created, but no AgentBasicInfo and AgentPremiumInfo instances have been created. This leads me to believe something is getting stuck at or after agent_basic_info = basic_info_form.save(commit=False), but I can't figure out what it is.
Here is my code:
views.py
def signup(request):
if request.user.is_authenticated:
return HttpResponseRedirect('../dashboard/')
if request.method == 'POST':
signup_form = SignupForm(request.POST)
basic_info_form = AgentBasicInfoForm(request.POST)
if signup_form.is_valid() and basic_info_form.is_valid():
agent = signup_form.save(commit=False)
agent.is_active = False
agent.save()
# Creates a basic info form with user input
agent_basic_info = basic_info_form.save(commit=False)
agent_basic_info.agent = agent
agent_basic_info = agent_basic_info.save()
# Creates a profile model with the agent's premium information, empty except for 'agent' field. No actual form displayed on sign up page.
agent_premium_info = AgentPremiumInfo.objects.create(agent=agent)
agent_premium_info.save()
current_site = get_current_site(request)
message = render_to_string('acc_active_email.html', {
'agent':agent,
'domain':current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(agent.pk)),
'token': account_activation_token.make_token(agent),
})
mail_subject = 'Activate your blog account.'
to_email = signup_form.cleaned_data.get('email')
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
return HttpResponse('Please confirm your email address to complete the registration')
else:
signup_form = SignupForm()
basic_info_form = AgentBasicInfoForm()
return render(request, 'signup.html', {'signup_form': signup_form, 'basic_info_form': basic_info_form})
def activate(request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
agent = Agent.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, Agent.DoesNotExist):
agent = None
if agent is not None and account_activation_token.check_token(agent, token):
agent.is_active = True
agent.save()
login(request, agent)
# return redirect('home')
return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
else:
return HttpResponse('Activation link is invalid!')
models.py
class Agent(AbstractUser):
pass
class AgentBasicInfo(models.Model):
TITLE = (
('Salesperson', 'Salesperson'),
('Sales Representative', 'Sales Representative'),
('Broker', 'Broker'),
('Broker of Record', 'Broker of Record'),
)
agent = models.OneToOneField(Agent, on_delete=models.CASCADE)
agent_first_name = models.CharField(max_length=30)
agent_last_name = models.CharField(max_length=30)
agent_preferred_email = models.EmailField()
office_phone_number = models.CharField(max_length=10)
agent_brokerage = models.CharField(max_length=50)
agent_title = models.CharField(max_length=20, choices=TITLE)
class AgentPremiumInfo(models.Model):
agent = models.OneToOneField(Agent, on_delete=models.CASCADE)
agent_phone_number = models.CharField(max_length=10, blank=True, null=True)
agent_website = models.CharField(max_length=50, blank=True, null=True)
agent_biography = models.TextField(blank=True, null=True)
agent_address_street = models.CharField(max_length=50, blank=True, null=True)
agent_address_city = models.CharField(max_length=25, blank=True, null=True)
agent_address_province = models.CharField(max_length=2, choices=PROVINCE, blank=True, null=True) # Add province choices later
agent_address_postal_code = models.CharField(max_length=6, blank=True, null=True)
agent_picture = models.ImageField(height_field=200, width_field=100, blank=True, null=True)
forms.py
class SignupForm(UserCreationForm):
email = forms.EmailField(max_length=200, help_text='Required')
def clean_email(self):
data = self.cleaned_data['email']
if not data.endswith('#gmail.com'):
raise forms.ValidationError("You must use your #gmail.com Email")
return data
class Meta:
model = Agent
fields = ('username', 'email', 'password1', 'password2')
class AgentBasicInfoForm(forms.ModelForm):
class Meta:
model = AgentBasicInfo
fields = ['agent_first_name', 'agent_last_name', 'agent_preferred_email', 'office_phone_number', 'agent_brokerage', 'agent_title']
class AgentPremiumInfoForm(forms.ModelForm):
class Meta:
model = AgentPremiumInfo
fields = ['agent_phone_number', 'agent_website', 'agent_biography', 'agent_picture', 'agent_address_street', 'agent_address_city', 'agent_address_province', 'agent_address_postal_code']
It seems something was wrong in the database despite me doing a reset_db. I did another reset_db and it magically fixed the issue.
When the user is required to fill his profile, he picks a city from the Google Places Autocomplete and posts the form, in the view I extract the city Id from the Google API based on the posted text (I use the same id as pk in my db) and try to extract a city from my db.
These are the models:
class City(models.Model):
#extracted from the Google API
city_id = models.CharField(primary_key=True, max_length=150)
name = models.CharField(max_length=128, blank=True)
country = models.CharField(max_length=128, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', primary_key=True)
city = models.ForeignKey(City, blank=True, null=True)
prof_pic = models.ImageField(blank=True, upload_to='profile_pictures')
This is the view:
def createprofile(request):
if request.method =='POST':
user = User.objects.get(username=request.user.username)
user_form = UserForm(data=request.POST, instance=user)
profile_form = UserProfileForm(data=request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save()
user.save()
profile = profile_form.save(commit=False)
profile.user = user
#brings back the city search result as text
searched_city = request.POST['city']
#brings back city ID from the Google API
searched_city_id = population_script.get_city_json(searched_city.replace(" ", ""))['results'][0]['id']
#If it's a valid city
if searched_city_id != -1:
city = City.objects.get(city_id = searched_city_id)
profile.city = city#this is what I want to happen!
else:
return HttpResponse("There's no such city, please try a different query.")
if 'prof_pic' in request.FILES:#now save the profile pic
profile.prof_pic = request.FILES['prof_pic']
print("PROF PIC IS: " + profile.prof_pic.url)
else:
profile.prof_pic = 'images/anon.png'
profile.save()
if 'next' in request.GET:
return redirect(request.GET['next'])
else:
print (user_form.errors, profile_form.errors)
else:
user_form = UserForm()
profile_form = UserProfileForm()
return render(request,
'excurj/createprofile.html', {'user_form':user_form, 'profile_form':profile_form})
However, I keep receiving an error that what's been posted is just text while the city needs to be a City object. I can save the profile pic ok though.
Cannot assign "'Dubai - United Arab Emirates'": "UserProfile.city"
must be a "City" instance.
edit: these are the forms:
class UserForm(forms.ModelForm):
first_name = forms.CharField(
label = "First Name:",
max_length = 80,
required = True
)
last_name = forms.CharField(
label = "Last Name:",
max_length = 80,
required = True,
)
class Meta:
model = User
fields = ('first_name', 'last_name')
class UserProfileForm(forms.ModelForm):
city = forms.CharField(
label = "Your Current City:",
max_length = 200,
required = True,
)
class Meta:
model = UserProfile
fields = ('city','prof_pic', 'dob', 'sex', 'education', 'career', 'about_you',
'music_movies_books', )
Please provide a related_name to the city field in the UserProfile.
I worked around this by creating a new UserProfile field called city_search_text which saves the searched text thus it of course does not return any error. I then receive it in the POST request and comfortable pull the proper city in the view.
I handled a similar issue by overriding my forms' clean method. Something like the following will work:
def clean(self):
# fix city problem
if self.cleaned_data.get("city") is not None:
self.cleaned_data['city'] = City.objects.get(id=self.cleaned_data.get("city"))
return self.cleaned_data
My forms' are never fully instantiated, particularly ImageFields (as how they would be like in admin), and TextFields (which appear as tags)
However, all other fields are instantiated properly.
May I know why this happens?
Attached code that replicates this issue:
#view
sellerForm = SellerUpgradeForm(instance=userseller_prof)
#form
class SellerUpgradeForm(ModelForm):
def __init__(self,*args, **kwargs):
super(SellerUpgradeForm, self).__init__(*args, **kwargs)
self.fields['selling_currency'].queryset = Countries.objects.filter(paypal_valid="yes")
class Meta:
model = UserSeller
fields = ("seller_store_image","shop_description","selling_currency","auth_username",
"auth_password","auth_signature")
#model
class UserSeller(models.Model):
user = models.ForeignKey(User, verbose_name="User")
access_plan = models.ForeignKey(AccessPlan, verbose_name="Seller's Access Plan")
status = models.CharField(max_length=100, choices=SELLER_STATUS, verbose_name="Seller Status", default="unapproved")
rating = models.DecimalField(verbose_name="Rating (Upon 5)", decimal_places=2, max_digits=4, default=0)
total_rating_count = models.IntegerField(verbose_name="Rating Count", default=0)
selling_currency = models.ForeignKey(Countries, verbose_name="Currency that seller's product will be sold in")
meta = models.ManyToManyField(UserMeta, verbose_name="User Meta Data", blank=True, related_name="seller_meta")
shop_description = models.TextField(verbose_name="Description about the products you are selling")
seller_store_image = models.ImageField(upload_to=settings.CUSTOM_UPLOAD_DIR, verbose_name="Store Avatar", blank=True)
internal_note = models.TextField(verbose_name="Seller Note (Internal Use Only)", blank=True)
timestamp = models.DateTimeField(verbose_name="Date Created", auto_now_add=True)
auth_username = models.CharField(max_length=50, verbose_name="Paypal Auth Username")
auth_password = models.CharField(max_length=50, verbose_name="Paypal Auth Password")
auth_signature = models.CharField(max_length=100, verbose_name="Paypal Auth Signature")
class Meta:
verbose_name = "Seller Profile"
verbose_name_plural = "Seller Profiles"
app_label = "management"
def __unicode__(self):
return "Rating: " + str(self.rating)
The reason why the ImageFields are not pre-populated is a browser security issue, nothing to do with Django.
If browsers let web pages pre-populate file inputs, it would be very easy for a site to arbitrarily upload content from a user's hard drive without their consent. So in order to stop that happening, file input fields are always rendered as blank.
Oops. I had a JS script that auto clears textareas on document.ready hence the following. forgot about this.
My bad.