I'm working on a simple project that requires a candidate to enter a description of his/herself. Since this requires lots of text, I use models.TextField() in models.py file and <textarea> tag in html.
In models.py
class Candidate(Person, models.Model):
#Other fields
description = models.TextField()
def __str__(self):
return self.name
In Html
<!---some stuff up-->
<label for="candidate_description">Describe yourself</label>
<textarea id="candidate_description" placeholder="Description..."></textarea>
<!---some stuff below-->
views.py file
def regCandidate(request):
if request.method == 'POST':
candidate = Candidate(
description=request.POST.get('candidate_description'),
)
candidate.save()
return render(request, 'Election/registerCandidate.html', {})
When I run the server and try to submit I get a IntegerityError. I have did some done some research and
found out the error is occurs when django receives a null value when it is required. I'm a beginner at django and I'm using custom forms. Can anyone help explain why django is receiving a None and how to fix it.
Fields in form are passed on name and value basis. I am guessing that since you don't have a name attribute in Textarea, therefore that field is not passed to server.
Try adding a name attribute to the textarea field:
<textarea id="candidate_description" name="candidate_description" placeholder="Description..."></textarea>
Related
I am creating a single page blog site how can i get the post id to the comments form , I created django forms with necessary field but the problem is ,I have to select the post id from a drop down menu manually while commenting, for that I passed post object as an input value of a form to views.py file but django needs instance to save in database what should I do now
note :I am not using post_detail
models.py
class comments(models.Model):
name=models.CharField(max_length=255)
content=models.TextField()
post=models.ForeignKey(blog,related_name="comments",on_delete=models.CASCADE)
#blog is the model to which comment is related
date=models.DateTimeField(auto_now_add=True)
forms.py
class commentform(ModelForm):
class Meta:
model=comments
fields=('name','content','post')
widgets={
'name' : forms.TextInput(attrs={'class':'form-control','placeholder':'type your name here'}),
'content' : forms.Textarea(attrs={'class':'form-control'}),
'post' : forms.Select(attrs={'class':'form-control'})
}
Html
<form method='POST' action="comment_action" class='form-group'>
{%csrf_token%}
{{form.name}}
{{form.content}}
<input type="text" id="objid" name="objid" value="{{objs.id}}" hidden>
<button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
views.py
def comment_action(request):
name=request.POST.get('name')
content=request.POST.get('content')
objid=request.POST.get('objid')
to_db=comments.objects.create(name=name,content=content,post=objid)
print(name,content,objid)
return redirect('/homepage')
return render(request, 'index.html')
ERROR :
Exception Type: ValueError
Exception Value:
Cannot assign "'48'": "comments.post" must be a "blog" instance.
-->48 is my blog id
i know that database will only accept instance post field because that was a foreign key
my question is how to pass it ?
You assign it with post_id:
def comment_action(request):
name = request.POST.get('name')
content = request.POST.get('content')
objid = request.POST.get('objid')
to_db = comments.objects.create(name=name, content=content, post_id=objid)
print(name,content,objid)
return redirect('/homepage')
Note: It is better to use a Form [Django-doc]
than to perform manual validation and cleaning of the data. A Form will not
only simplify rendering a form in HTML, but it also makes it more convenient
to validate the input, and clean the data to a more convenient type.
Note: normally a Django model is given a singular name, so Comment instead of comments.
Apologies for the long post, I am trying to implement a simple button which either add or remove an item from a watchlist.
While I initially managed to implement the "addwatchlist" function appropriately, I tinkered my code for a few hours and I somewhat am unable to wrap my head around it again.
Here is the error that I receive when pressing the "Add to Watchlist" button :
TypeError at /addwatchlist/10
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Request Method: GET
Request URL: http://127.0.0.1:8000/addwatchlist/10
Django Version: 3.1.1
Exception Type: TypeError
Exception Value:
Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
TRACEBACK :
watchlist.save()
▼ Local vars
Variable Value
id 10
request <WSGIRequest: GET '/addwatchlist/10'>
watchlist Error in formatting: TypeError: Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">.
Here is the error that I receive when pressing the "Remove from Watchlist" button, note that this is the exact same error I initially received which in turn forced me to try and tweak the way "Add to Watchlist" function :
AssertionError at /removewatchlist/1
Watchlist object can't be deleted because its id attribute is set to None.
Request Method: GET
Request URL: http://127.0.0.1:8000/removewatchlist/1
Django Version: 3.1.1
Exception Type: AssertionError
Exception Value:
Watchlist object can't be deleted because its id attribute is set to None.
TRACEBACK :
watchlist.delete()
▼ Local vars
Variable Value
id 1
request <WSGIRequest: GET '/removewatchlist/1'>
watchlist <Watchlist: 1 Watchlist ID# None : admin watchlisted : "Iphone 11 Pro">
Models.py :
class Listing(models.Model):
owner = models.CharField(max_length=64)
title = models.CharField(max_length=64)
description = models.TextField(max_length=1024)
startingBid = models.IntegerField()
link = models.URLField(blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_id")
time = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(null=False, default='True')
def __str__(self):
return f'"{self.title}"'
class Watchlist(models.Model):
user = models.CharField(max_length=64)
watchlist_listingid = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="watchlist_listingid", null=True)
def __str__(self):
return f'{self.watchlist_listingid_id} Watchlist ID# {self.id} : {self.user} watchlisted : {self.watchlist_listingid}'
Views.py :
#login_required
def watchlist(request):
if request.user.username:
return render(request, "auctions/watchlist.html", {
"items" : Watchlist.objects.filter(user=request.user.username),
"watchlist_listingid" : Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user=request.user.username),
"watchlist_count" : len(Watchlist.objects.filter(user=request.user.username))
})
def addwatchlist(request, id):
if request.user.username:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
watchlist.save()
return redirect('listing', id=id)
else:
return render('auctions/watchlist.html', {})
def removewatchlist(request, id):
if request.user.username:
# all_entries = Watchlist.objects.values_list('watchlist_listingid', flat=True).filter(user='admin')
# for i in all_entries:
# if i == id:
# removeMe = i
# else:
# removeMe = 'TEST ERROR'
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
watchlist.delete()
return redirect('listing', id=id)
Urls.py :
path("listing/<int:id>", views.listing, name="listing"),
path("watchlist", views.watchlist, name="watchlist"),
path("addwatchlist/<int:id>", views.addwatchlist, name="addwatchlist"),
path("removewatchlist/<int:id>", views.removewatchlist, name="removewatchlist")
The Add/Remove buttons are hosted on listing.html :
{% if user.is_authenticated %}
<p>
{% if watchlisted %}
<button class="btn btn-danger">Remove from watchlist</button>
{% else %}
<button class="btn btn-success">Add to watchlist</button>
{% endif %}
</p>
{% endif %}
Questions :
Am I complicating myself by using Watchlist.watchlist_listingid as a
foreign key for Listing? I have seen quite a few other posts where
people would tend not to use foreign keys at all, though I believe it
might be not optimized.
I could eventually be using a form to envelop those buttons but I
already have a form in my listing.html page (to add comments).
Tackling the data from a form might be easier though I have not
managed to find out how to implement several distinct Django forms on
one HTML page. What do you consider best practice? Forms or direct
link summoning functions?
For "Add to Watchlist", why do I obtain def __str__(self):return f'"{self.title}"'instead of simply adding a new entry in my
Watchlist table?
Finally, for "Remove from Watchlist",why do i receive "id attribute is set to None" when I know for a fact that on my example,the
Listing.id for "Iphone 11 Pro" is 1, and technically speaking,
Watchlist.id (which does not show at all in my model) is 11 based on
the Django Admin page. Even hardcoding 11 to force its deletion off
the Watchlist table still returns this "none" error.
Looking forward to your replies!
Per your specific error, the problem is in this line, which should be clear from the error message:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first())
id is expecting an int (the id of a Model instance), but you are actually passing it an object instance of your Listing model; filter() returns a queryset, and then .first() returns the first object in that queryset.
That's why the error is telling you that "field id", which expects an int, is instead getting "<Listing: "Gloss + Repair Spray">" -- that's an actual instance of your Listing model.
Solution:
Simply adding .id after the .first() should fix your problem, so you are actually passing the id of that object to the id field:
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id)
That should resolve your specific issue; having said that, though, I highly recommend you use forms for these buttons. Currently, you are allowing modification of the contents of your database in an insecure fashion. Any activity that modifies database content should be delivered as a POST request. You can do this with forms quite easily, even if you aren't using any actual input-fields. The button will submit the empty form as a POST request, you can include a csrf token in your form tag in the template, and the db will be modified accordingly. Another option, if you really don't want to use forms, is to use AJAX calls (via jQuery), which can be sent as POST requests without the use of forms. Explaining how to do that is beyond the scope of this response, but there is plenty of info here on SO.
Lastly, if a Watchlist is tied to a User, you should probably consider a different database schema entirely for getting results-- that is, one that actually ties the User model to the item they are adding to their watchlist (it's possible, though, that I'm misunderstanding your intent here, and maybe you don't want that). You could connect the User model and the Listing models via a Many-to-Many relationship, since a User can 'watchlist' many listings, and a listing can be 'watchlisted' by many users.
Fix for : "Field 'id' expected a number but got <Listing: "Gloss + Repair Spray">."
Simply add .id after .first()
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first() = <Listing: "Gloss + Repair Spray">
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=Listing.objects.filter(id=id).first().id) = 6 (which is the ID in my database)
Fix for : Watchlist object can't be deleted because its id attribute is set to None.
watchlist = Watchlist.objects.get(user=request.user.username, watchlist_listingid=id)
GET the whole row in your table and assign it to a variable before using delete()
Instead of the faulty :
watchlist = Watchlist(user=request.user.username,watchlist_listingid_id=id)
Finally, I found out that to use different Django Forms in the same HTML page, you can set the action of the form to a path url in URLS.PY which would then trigger a special functions in Views.py.
We use Django with Crispy forms in our websites. I have a form rendered by {% crispy form %}. Here is the code of the form:
class ProfileForm(AddAttributesToFieldsMixin, CleanDateOfBirthMixin, LocalizedFirstLastNameMixin, forms.ModelForm):
profile_picture = forms.ImageField(required=False, widget=CustomPhotoWidget, label=_('Update your profile picture'), error_messages={'required': _("A profile picture is required.")})
class Meta:
model = User
fields = ('slug', 'gender', 'date_of_birth', 'profile_picture')
The form uses CustomPhotoWidget which is defined like this:
class CustomPhotoWidget(forms.widgets.Widget):
def render(self, name, value, attrs=None, renderer=None):
return render_to_string(template_name='accounts/edit_profile/widgets/photo_widget.html', context={
'name': name,
'user_photo': self.attrs['user'].photo,
})
But the problem is, when I upload a file from my browser, I receive an error message "No file was submitted. Check the encoding type on the form." and the file is not saved. The encoding type of the form is incorrect. How do I change the encoding type with Crispy forms?
I submitted an issue on GitHub and asked for help from a developer I work with, Aaron Chong. He checked and found out the problem. CustomPhotoWidget should be defined like this:
class CustomPhotoWidget(forms.widgets.Widget):
needs_multipart_form = True
def render(self, name, value, attrs=None, renderer=None):
return render_to_string(template_name='accounts/edit_profile/widgets/photo_widget.html', context={
'name': name,
'user_photo': self.attrs['user'].photo,
})
needs_multipart_form = True is what was needed to fix the encoding type of the form. I'm uploading this solution to Stack Overflow because I didn't find needs_multipart_form documented in other questions on the website.
I am trying to implement a tagging process for profiles so you can add your hobbies for example.
I have chosen django-taggit as it seemed quite simple and does what I need it to, plus don't really know how to do it myself from scratch.
I have managed to make it work to some extent but I am having issues with 3 things:
Not really sure what's the best way to control the form field for these tags as I generate the form automatically with widget adjustments in meta function of the form, but it might work fine after resolving the below two issues.
When there is no data for the field hobbies (tags) the field gets populated with a single tag of value "[]" as per below image.
When I add a tag of "music" and submit the form after I reload the page I get this "[]" as per image. I assumed this will be dealt with by the library, but I cannot see another similar scenario online.
When I try adding another tag of "games" and save and reload, the below happens. The initial value gets wrapped again.
My model is:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
hobbies = TaggableManager()
My form is:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['hobbies',]
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['hobbies'].widget = forms.TextInput()
self.fields['hobbies'].widget.attrs['data-role'] = "tagsinput"
self.fields['hobbies'].widget.attrs['class'] = "form-control"
self.fields['hobbies'].required = False
My view function is:
if request.method == 'POST':
user_profile = UserProfile.objects.get(user=request.user)
form = UserProfileForm(request.POST, instance=user_profile)
print(form)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
print("Form valid")
form.save_m2m()
Using:
<script src="/static/js/tagsinput.js"></script>
<link rel="stylesheet" href="{% static 'css/tagsinput.css' %}" />
I had this exact same problem.
One solution is to apply the data-role="tagsinput" AFTER you turn a list of tags into a comma-separated string for the form.
Here is that solution:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, **kwargs):
self.fields['tags'].widget.attrs['value'] = ", ".join(list(self.instance.tags.names()))
self.fields['tags'].widget.attrs['data-role'] = "tagsinput"
Output:
As you can see, there's a problem with quotes appearing around tags that are multi-word. It also causes new tags with quotes to be saved to the database.
If double-quotes didn't appear around multi-word phrases, this would be the most elegant solution. If someone solves this in the future, drop a note!
My template is this:
<div class="m-3 p-3 border">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">Save Form</button>
</form>
</div>
I know I can use a template tag to strip the extra quotes from the tag field itself, but then I'd have to go through and create all the form fields manually just to set the tags template tag.
For the time being, my solution is to simply use Javascript and just modify the Meta widgets section of the form.
FINAL ANSWER (for now):
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'tags': forms.TextInput(attrs={
"data-role": "tagsinput",
})
}
custom.js - put this script on the page that loads the form.
document.addEventListener("DOMContentLoaded", function(event) {
let tags_input = document.querySelector('#id_tags');
let tags_input_value = tags_input.value;
let new_value = [...tags_input_value.matchAll(/<Tag:\s*([\w\s]+)>/g)].map(([, m]) => m).join(', ')
tags_input.setAttribute('value', new_value);
}
So all we're doing is modifying the front-end presentation, and leaving all the backend internal forms functionality untouched.
So after quite a few (hundreds) of tests, I finally narrowed down where the issue was and tried to go around it with successful result.
It seems the data got amended into tag objects through tagsinput library I was using. Only when the "data-role" was specified as "tagsinput" in the forms.py the data would already come to html side as those objects and be shown incorrectly. So instead I wanted to keep the data clean and only apply data-role='tagsinput' in the end for visual aspect, which I did using:
var hobbiesTags = document.getElementById("id_hobbies");
if(hobbiesTags){
var att = document.createAttribute("data-role");
att.value = "tagsinput";
hobbiesTags.setAttributeNode(att);
};
And that resulted in the below. Maybe there are better ways to do this, I'm not sure, but it's a pretty clean solution. Share your alternatives.
PLEASE IGNORE!
Trying to use UpdateView to update a model object with name "Tag"
Here is the model:
NAME_REGEX = r'^[a-zA-Z0-9\s.\-]+$'
class Tag(TimeStampModel):
name = models.CharField(max_length=40,
validators = [
RegexValidator(regex=NAME_REGEX,
message='name must be alphanumeric, may contain - _',
code='invalid tag name')],
unique=True)
slug = models.SlugField(max_length=60, unique=True, blank=True)
text = models.TextField(blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
url = reverse('tags:tag_detail', kwargs={'slug':self.slug})
print('Tag *************** 010 ********************')
print(url)
return url
def save(self, *args, **kwargs):
self.slug = make_slug(self.name)
super().save()
I am able to create a Tag using CreateView, list the Tags using ListView and access the Tag detail using DetailView. I am also able to edit the tag using a function based view. However, it falls down with UpdateView.
Here are the urls:
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('tag_list/', views.TagListView.as_view(), name='tag_list'),
path('tag_detail/<slug:slug>/', views.TagDetailView.as_view(),
name='tag_detail'),
path('create_tag/', views.CreateTagView.as_view(),
name = 'create_tag'),
path('update_tag/<slug:slug>/', views.UpdateTagView.as_view(),
name = 'update_tag'),
path('category_list/', views.CategoryListView.as_view(),
name = 'category_list'),
path('category_detail/<slug>/', views.CategoryDetailView.as_view(),
name = 'category_detail'),
]
In a template called "tag_detail.html" I provide a link that should take the user to an edit form as follows:
<p class="slm-red">
{{ tag_dict.slug }}
<br>
Edit Tag
</p>
(I've printed the value of the slug before the link. That is not the problem. The slug value is correct. in this case slug = "brown")
When I click the link it takes me to:
http://localhost:8000/tags/update_tag/brown/
In this case the slug has the value "brown"
I get the following error message:
Reverse for 'update_tag' with no arguments not found. 1 pattern(s) tried: ['tags\\/update_tag\\/(?P<slug>[-a-zA-Z0-9_]+)\\/$']
I tried it the old fashioned way using "url" instead of path and a regular expression for the slug with the same result.
It all works fine when I use a function based view passing slug as a parameter.
Here is the UpdateTagView:
class UpdateTagView(UpdateView):
print('UpdateTagView ************** 010 *****************')
template_name = 'tags/update_tag.html'
model = Tag
fields = ['text']
The first line is a print statement. It does not appear on the console log so this confirms that Django never gets as far as the actual view. What I do get on the console log includes the following:
[22/Jan/2018 16:06:00] "GET /tags/tag_detail/brown/ HTTP/1.1" 200 4852
Internal Server Error: /tags/update_tag/brown/
I've tried several variants but nothing works. I am getting quite desperate.
I've seen similar questions on stack overflow for previous versions of Django but there never seems to be an answer.
When I use an FBV it's simple. I just pass the slug as a parameter along with request.
For what it's worth the errors seems to be occurring in base.py. At the end of the error messages I see:
C:\django\_dj\projenv\lib\site-packages\django\urls\base.py in reverse
I think there's a kwarg I'm supposed to override but I have no notion how to do it. Since Django never gets as far as the UpdateTagView nothing I do there can affect the outcome.