I have a Comment Model that uses ContentType and GenericForeignKey so that a comment can be attached to an instance of an arbitrary model:
class Comment(models.Model):
...
text = models.TextField()
target_content_type = models.ForeignKey(ContentType, related_name='comment_target',
null=True, blank=True)
target_object_id = models.PositiveIntegerField(null=True, blank=True)
target_object = GenericForeignKey("target_content_type", "target_object_id")
And I have a form for creating Comments:
class CommentForm(forms.Form):
new_comment = forms.CharField(widget=forms.Textarea(attrs={'rows':2}))
The form is used like this:
<form method="POST" action="{% url 'comments:create' %}">{% csrf_token %}
{{ form | crispy }}
<input type='hidden' name = 'target_id' value='{{q.id}}' />
<input type='submit' class='btn btn-primary' value='Add reply' />
</form>
In my Create view, how can I get the model of the originating object, or the ContentType & id, so that I can create a new Comment?
def comment_create(request):
if request.method == "POST":
if form.is_valid():
text = form.cleaned_data.get('new_comment')
target_object_id = request.POST.get('target_id')
target_content_type = ?
...
When you generate the form, add a hidden field that also includes the content type id.
From there you can then query the in-built Django ContentType model, like so:
from django.contrib.contenttypes.models import ContentType
my_model = ContentType.objects.get(id=some_id).model_class()
As for how to get the content type id in there, because you can't use methods that take arguments you can either change how your form is constructed (adding a hidden field and populating a default (better way) or creating a template tag that would act as a helper function to populate a hidden field in the html template (easier way).
In either case, you can then just use the ContentType again to call this:
ct = ContentType.objects.get_for_model(my_item)
Credit to this question.
Related
I am trying to a update a form from a separate function.
By clicking on button "Add" the existing form is updated by adding a user to the form.
Adding the user to the form works fine. However I am loosing the text input from the initial form when submitting my update.
The reason why I am using 2 functions is because I have multiple forms on the same template: each form is redirected to a specific url defined in action="{% url %}"
The usual way I use to update a form is as follows:
def function(request, id)
instance = get_object_or_404(Model, pk=id)
data = Model(request.POST or None, instance = instance)
This is not working in this case because I need to provide the instance_id on the parent function, but the id parameter is provided on the function that supports the form. (child function)
I suppose there is 2 options/questions:
Can I access the form_id from the parent function?
Should I deal with this in the form function? and in this case how do I keep the
existing text when updating form?
model
class UserProfile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
class Model(models.Model):
user = models.ManyToManyField(User, blank=True)
text = models.TextField('text', blank=True)
template (main/parentfunctiontemplate.html)
{%for model in a %}
<form action="{%url 'child-function' userprofile.id model.id%}" method="POST">
{% csrf_token %}
<input type="Submit" value="Add" class="btn btn-primary custom-btn">
</form>
{%endfor%}
view
def parent_function(request, userprofile_id):
a = Model.objects.filter(venue=request.user.userprofile.venue)
updateform=ModelForm()
return render(request,"main/parentfunctiontemplate.html",{'updateform':updateform,'a':a})
def child_function(request, userprofile_id,model_id):
url = request.META.get('HTTP_REFERER')
userprofile = get_object_or_404(UserProfile, pk=userprofile_id)
instance = get_object_or_404(Model, pk=model_id)
updateform = ModelForm(request.POST or None, instance = instance)
if updateform.is_valid():
data = updateform.save(commit=False)
data.save()
updateform.save_m2m()
current_model_instance = get_object_or_404(Model, id=data.id)
current_model_instance.user.add(userprofile.id)
return redirect(url)
else:
print(updateform.errors)
return redirect('main/parentfunctiontemplate.html',{'userprofile':userprofile,'instance ':instance })
form
class ModelForm(ModelForm):
class Meta:
model = Model
fields = ('text')
I need two of the three form fields to be filled in automatically before submitting to the database. Slug is supposed to be filled based on test_name, and user have to be filled based on data about the current user.
Now I can submit the form, but the database entry will not be created.
models.py
class Test(models.Model):
test_name = models.CharField(max_length=100, db_index=True, verbose_name='Test name')
slug = models.SlugField(max_length=100, unique=True, verbose_name='URL')
author = models.ForeignKey(User, db_column="user", on_delete=models.PROTECT)
forms.py
class AddTestForm(forms.ModelForm):
class Meta:
model = Test
fields = ['test_name', 'slug', 'author']
views.py
def ask_test_name(request):
form = AddTestForm(request.POST)
if form.is_valid():
test = form.save(False)
test.slug = slugify(test.test_name)
test.author = request.user
test.save()
return render(request, 'app/ask_test_name.html', {'form': form})
ask_test_name.html
<form action="{% url 'ask_test_name' %}" method="post">
{% csrf_token %}
<p><input type="text" name="test_name" required></p>
<p><input type="hidden" name="slug"></p>
<p><input type="hidden" name="author"></p>
<p><button type="submit">Create</button></p>
</form>
Updated
It seems to me the problem is that the html does not see what model I want to use, but I don't know how to solve it here, I need two fields to be hidden
Remove slug and author from the fields in forms.py and don't include them in your HTML form. That should do the trick. You're only hitting the DB after already assigning values to slug and author, so that shouldn't throw an error.
I'm trying to get a simple form working. Oddly, other forms I wrote in this app are working fine, but this one wont show the fields. Can anyone tell me what I'm missing? Here are the files
views.py:
def newnote(request, record_id):
if request.method == 'POST':
form = NoteForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/tracker/all/')
else:
form = NoteForm()
return render(request, 'tracker/noteform.html', {'form': form})
models.py
class Note(models.Model):
record = models.ForeignKey(Record, on_delete=models.CASCADE)
note_text = models.CharField('Notes', max_length=2000)
note_date = models.DateField('Date Entered')
forms.py
class NoteForm(forms.Form):
class Meta:
model = Note
fields = ['note_text',
'note_date'
]
template (noteform.html)
<form action="/tracker/newnote/" method="post">
<div id="fields">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</div>
</form>
One other note, I have commented out the div id called "fields", to rule out CSS as the issue.
Your form is based on form.Form, which doesn't know anything about models, doesn't expect a Meta class, and expects all its fields to be declared manually - since you have not declared any fields, nothing will show on the template.
It should inherit forms.ModelForm instead.
I am writing simple blog in django and I gotstuck on uploading files.
I want to create template wher user can write post and add multiple files. So firstrly i have created models:
class Post(models.Model):
title= models.CharField(verbose_name=u"post title", max_length=40, blank=False)
body= models.TextField(verbose_name=u"post body", blank=False)
author= models.ForeignKey(User, blank=False)
published= models.DateTimeField(verbose_name=u"data publikacji", blank=True, null=True)
class Add(models.Model):
post=models.ForeignKey(Post)
file=models.FileField(upload_to='adds', verbose_name=u"added files")
and after that i created template which looks like this:
{% block content %}
<div class="col-lg-6 col-md-offset-3">
<form enctype="multipart/form-data" method="post" adction="{% url 'create_post' %}">{% csrf_token %}
{{post_form|crispy}}
<input type="file" name="files" multiple/></br>
<input class="btn btn-success" style="width:100%" type="submit" value="Dodaj post"/>
</form>
</div>
{% endblock %}
after that i wrote simple view which would handle files and post creation:
def create_post(request):
if request.method== 'GET':
post_form=PostForm()
add_form=AddForm()
context={
'post_form': post_form,
}
return render(request, 'create_post.html', context)
else:
post_form=PostForm(request.POST)
if post_form.is_valid():
instance=post_form.save(commit=False)
instance.published=datetime.datetime.now()
instance.author=request.user
instance.save()
# import ipdb
# ipdb.set_trace()
for file in request.FILES.getlist("files"):
add_instance=AddForm()
add_instance.post=instance
add_instance.file=file
if add_instance.is_valid():
add_instance.save()
return redirect(reverse('AllPosts'))
but none of add_instance passes the validation "is_valid". But why?
And the second question is there any way to use Form in template against writing raw input for file? When i use AddForm i can only add one file what is logic.
You problem is that your form is always an unbounded form, which means that it is initialized without data. By data I mean POST or GET data from request or an instance of the model. unbounded form will always fail is_valid() and it doesn't make sense to manually assign anything to a form instance like add_instance = AddForm() then add_instance.post = instance.
django doc explains how to use a form.
You cannot upload multiple files with one single form because each form is a Add model form. You should really use ModelFormSet for your AddForm to add multiple model instances in one submit.
django doc explains how to use ModelFormSet.
By the way, I find the model name Add extremely confusing, consider changing it to Attachment or PostFile or something else.
Edit:
I didn't try it, but if you want one widget for multiple files, you can do this in views:
for upload_file in request.FILES.getlist('files'):
form = AddForm(request.POST, {'file': upload_file})
if form.is_valid():
new_file = form.save(commit=False)
new_file.post = instance
new_file.save()
I am working on an app that has a section with with a file upload form for .txt fiels. I would like for the current user that is uploading the file to be added along with the file and the file name. Currently, I can do this successfully in the admin section but I just cant get it to save via the form itself. Any Ideas?
Here are the models:
class UploadedTextFile(models.Model):
file = models.FileField(upload_to="textfiles")
filename = models.CharField(max_length = 50)
username = models.ForeignKey(User, blank=True, null=True)
class UploadedTextFileForm(ModelForm):
class Meta:
model = UploadedTextFile
fields = ['file', 'filename']
Here is my view:
def inputtest(request):
#response for file being submited
if request.method == "POST":
form = UploadedTextFileForm(request.POST)
if form.is_valid():
new_form = form.save(commit=False)
new_form.username = request.user
new_form.save()
return render(request, 'about.html')
inputtest = UploadedTextFileForm()
return render(request, 'failed.html', {'inputtest': inputtest})
else:
inputtest = UploadedTextFileForm()
return render(request, 'inputtest.html', {'inputtest': inputtest})
Here is my html:
{% extends 'base.html' %}
{% block content %}
<form method="post">{% csrf_token %}
{{ inputtest.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock content %}
Doing it in the view (as you've shown) is the right way to do this. Most likely you're having problems because you've left username as a field on the form, and because the FK model field doesn't have blank=True set the form requires the field to be provided. You should explicitly declare just the subset fields that you want to accept user input for in the form's Meta class.
class UploadedTextFileForm(ModelForm):
class Meta:
model = UploadedTextFile
fields = ['file', 'filename']
I am not sure why you're rendering a different template when the form is not valid, but no matter what you're not providing the form object in the context. This means that you'll never see any errors the form detects, which is probably what's happening with this code - you're not seeing the error that username is not provided.