I have a model.py file that has classes Author and Article. Article has a foreign key referencing Author. I have created a view,blogCreate, using a form ,ArticleForm, in my forms.py file. Since author in class Article is a foreign key, it means that author will be chosen from the Author queryset. This means that the select tag will automatically used by the form, instead I want to use the <input type="text" > tag so that I can create an instance of Author using the input and not select from the queryset.
forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'content', 'aurthor')
widgets = {
'title': forms.TextInput(attrs={
'class': 'title'}),
'content': forms.Textarea(attrs={
'class': 'text_input',
'name': 'article_content'}),
# Changed to TextInput so it can use <input type="text" >
'aurthor': forms.TextInput(attrs={
'class': 'text_input',
'name': 'aurthor_name'})
}
models.py
from django.db import models
from ckeditor.fields import RichTextField
class Aurthor(models.Model):
name = models.CharField("Author Name", max_length=100)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField("Title", max_length=100)
content = RichTextField(blank=True, null=True)
pub_date = models.DateTimeField("Publish Date", auto_now_add = True)
aurthor = models.ForeignKey(Aurthor, on_delete=models.CASCADE)
def __str__(self):
return self.title
views.py
from .models import Article, Aurthor
from django.views.generic import CreateView
from .forms import ArticleForm
class blogCreate(CreateView):
model = Article
form_class = ArticleForm
template_name = 'BlogHandler/blog.html'
blog.html
<form action="" method="post">
{% csrf_token %}
{{form.as_p}}
<button type="submit">Post</button>
</form>
I finally found a way to do exactly what I wanted, I don't if how efficient it is but it works. Let me know if there is a better way.
forms.py
from django import forms
from .models import Article, Author
class ArticleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.fields['author'] = forms.CharField(max_length=100, required=True)# author is required
class Meta:
model = Article
fields = ('title', 'content', )
In the question, I stated that I'd changed the widget for author to TextInput so that I could enter text not an instance of Author.This didn't work out the way I wanted, so instead I removed author from fields and made a custom field author that is not a field in my model. This way I still get the text input to create my Author instance.
models.py
class Article(models.Model):
title = models.CharField("Title", max_length=100, null=False)
content = RichTextField(blank=True, null=False)
pub_date = models.DateTimeField("Publish Date", auto_now_add = True)
author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True)
I made author nullable in my models.py file but it is okay because I made the custom auhtor field in forms.py required so that all Articles made using the site have an Author. I was getting some error before this change
views.py
class articleCreate(CreateView):
model = Article
form_class = ArticleForm
def form_valid(self, form):
rt = super().form_valid(form)
article = form.save(commit=False)
author_name = self.request.POST['author'].title()
author, created = Author.objects.get_or_create(name=author_name)
article.author = author
article.save()
return rt
Here I first pause the save so that I can create an Author using the text input from the custom field author, which is simple text, if the Author instance already exists it gets else it creates it. Then I save and I'm done.
Related
Please tell me, when a model has a lot of related fields with other tables, how to make a normal form with filling such a model?
How do I create a form for Project?
class City(models.Model):
obl = models.CharField(max_length=255, choices=REGIONS, default="24", verbose_name="Регион")
name = models.CharField(max_length=128, verbose_name="Город")
population = models.IntegerField()
class Address(models.Model):
city = models.ForeignKey(City, on_delete=models.PROTECT, verbose_name="Город")
street = models.CharField(max_length=255, verbose_name="Улица")
numb = models.CharField(max_length=64, verbose_name="Номер дома")
class Project(models.Model):
manager = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="Сотрудник")
address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="Адрес")
vis = models.DateField(verbose_name="Подписан дата", blank=True)
accept = models.DateField(verbose_name="Принят дата", blank=True)
Maybe I need a step-by-step fill-in form
You can create and customize the admin form based on your needs. I.e. if you create an admin form for the Project and you would like to include the User form as an Inline form you can achieve that easily by inlines.
Please refer to the documentation regarding adding related objects inside forms.
https://docs.djangoproject.com/en/4.1/intro/tutorial07/
I would follow Jamal's answer if you only need the interface in the admin console.
If you want to create a custom interface in your own, I would do it like this (untested):
# forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['manager', 'vis', 'accept']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# When we're creating new instance
if not self.instance.pk:
self.address_form = AddressForm(
data=kwargs.get('data'),
files=kwargs.get('files'),
)
# When we're updating an existing instance
else:
self.address_form = AddressForm(
instance=self.instance.address,
data=kwargs.get('data'),
files=kwargs.get('files'),
)
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['street', 'numb']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# When we're creating new instance
if not self.instance.pk:
self.city_form = CityForm(
data=kwargs.get('data'),
files=kwargs.get('files'),
)
# When we're updating an existing instance
else:
self.city_form = CityForm(
instance=self.instance.city,
data=kwargs.get('data'),
files=kwargs.get('files'),
)
class CityForm(forms.ModelForm):
class Meta:
model = City
fields = '__all__'
# views.py
class CreateProject(CreateView):
model = Project
form_class = ProjectForm
template_name = 'project_form.html'
class UpdateProject(UpdateView):
model = Project
form_class = ProjectForm
template_name = 'project_form.html'
# project_form.html
<form method="post">
{% csrf_token %}
{# The project form #}
{{ form.as_p }}
<h2>Address</h2>
{# The address form in the project form. Instead of the address field. #}
{{ form.address_form.as_p }}
<h2>City</h2>
{# The city form in the address form. Instead of the city field. #}
{{ form.address_form.city_form.as_p }}
<input type="submit" value="Save" />
</form>
PS., if by any chance you're using django-crispy-forms package, make sure you don't render the form tags in the AddressForm and CityForm.
PSPS., if you come into a situation where two of the forms you're rendering on the same page have attributes with the same name, you will need to use prefix in your initializer of the forms to namespace these attributes. However, this is not the case in your question, so I didn't want to complicate.
When i try to upload an image in the admin panel of django, i get this Forbidden (CSRF token missing.): /ckeditorupload/ i have this result in my console.
here my model
from django.db import models
from django.contrib.auth.models import User
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField
# Create your models here.
STATUS = (
(0,"Draft"),
(1,"Publish")
)
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
updated_on = models.DateTimeField(auto_now= True)
#content = RichTextField(blank=True, null=True)
image = RichTextUploadingField()
#image = models.ImageField(upload_to='featured_image/%Y/%m/%d/') #
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
my views
from django.views import generic
from .models import Post
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
class PostList(generic.ListView):
queryset = Post.objects.filter(status=1).order_by('-created_on')
template_name = 'index.html'
class PostDetail(generic.DetailView):
model = Post
template_name = 'post_detail.html'
I tried to exempt the CSRF but i get same issues, & when i delete the middlewareCsrf it's dont works too, since i'm using the admin django and not a custom post method, i dont know where to pass the {{ csrf_token }}
Thanks for your help :)
I had a similar problem some time ago, it was as if all of a sudden there was no csrf_token available anymore in the user session. After a bit of tinkering this solved it.
import:
from django.middleware.csrf import get_token
in the view:
csrf_token = get_token(request)
context = {
"csrf_token": csrf_token,
}
In the template I still use {% csrf_token %}, but I also make some API calls in JavaScript and in those ones I instead used directly the token that i passed through the context -> {{ csrf_token }}
This is my first question here and I am writing because I'm going mad with this, even after reading documentation and a lot of answers here. Thank you very much and sorry for my bad english!
I have these models:
class Profile(models.Model):
name = models.CharField(max_length = 255, blank = False)
user = models.ForeignKey(User, blank = True, null = True)
class Category(models.Model):
name = models.CharField(max_length = 50, blank = False)
class ProfileCategory(models.Model):
profile = models.ForeignKey(Profile)
category = models.ForeignKey(Category)
class Meta:
unique_together = ('profile', 'category')
Is this model correct?
I suppose to have already the database with the categories saved. I need a page where the user can create a new profile and choose the categories from a list of checkbox. Should I use two form in the same page, one for the profile and one for choose the categories or a single form? I think i need a ModelMultipleChoiceField for the categories.
I also need a view that display the same form already filled with the profile and the categories where the user can change the profile name, and add or remove categories.
Tell me if you need more information and thank you very much.
ProfileCategory model is unnecessary here. Use ManyToMany field to achieve the same result:
class Category(models.Model):
name = models.CharField(max_length=50, blank=False)
class Profile(models.Model):
name = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, blank=True, null=True)
categories = models.ManyToManyField(Category, blank=True)
Now you can edit profile with a single form as any other model. The only thing you should to remember is to call save_m2m() in case if you override the form's save() method.
app/forms.py
from django import forms
from app.models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
widgets = {
'categories': forms.CheckboxSelectMultiple,
}
app/views.py
from django.views.generic.edit import CreateView
from app.forms import ProfileForm
from app.models import Profile
class ProfileCreate(CreateView):
form_class = ProfileForm
model = Profile
templates/app/profile_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
app/urls.py
from app.views import ProfileCreate
urlpatterns = patterns('',
...
url(r'^profile/create/', ProfileCreate.as_view()),
)
To update profiles use UpdateView with the same ProfileForm class and template.
EDIT: If you need additional fields in the ProfileCategory model then you can set it as intermediary model with through argument of the ManyToManyField. To edit such models you have to use formsets. Read more about it here, here and here.
I have the below models for which I'm trying to create a form for:
class Letter(models.Model):
title = models.CharField(max_length=100)
publish_date = models.TimeField()
class LetterRecipients(models.Model):
letter = models.ForeignKey(Letter)
recipient_name = models.CharField(max_length=200)
recipient_rating = models.IntegerField()
has_responded = models.BooleanField()
I'd like a single form that allows the user to enter a title and publish_date for the letter, and in the same form enter multiple recipients by name and rating.
Can anyone help with creating the form model for this? I can't figure out how to have django generate this form using {{ form.as_p }}. I think I'll have to use jQuery to create the additional recipient rows on the HTML page, but how would I get django to parse those into the model?
Any help would be greatly appreciated.
Thanks.
Ark
Ark, you can use ModelMultipleChoiceField in Django form. Here are some roughly example. I create "posts" apps just for quick testing :
forms.py
from django import forms
from django_test.posts.models import Letter, LetterRecipients
class LetterForm(forms.Form):
title = forms.CharField()
publish_date = forms.TimeField()
recepient = forms.ModelMultipleChoiceField(
queryset=LetterRecipients.objects.all()
)
models.py
from django.db import models
class Letter(models.Model):
title = models.CharField(max_length=100)
publish_date = models.TimeField()
class LetterRecipients(models.Model):
letter = models.ForeignKey(Letter)
recipient_name = models.CharField(max_length=200)
recipient_rating = models.IntegerField()
has_responded = models.BooleanField()
def __unicode__(self):
return self.recipient_name
views.py
# Create your views here.
from django_test.posts.forms import LetterForm
from django.shortcuts import render
def index(request):
form = LetterForm()
data = {'form': form}
return render(request, 'posts/index.html', data)
index.html
{% load url from future %}
{{ form.as_p }}
I am trying to create a delete function for my Workout model.
This is the model:
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def delete(self):
return reverse("delete_workout", kwargs = {'workout_id': self.workoutID})
Next I have the view:
def delete_workout(request, workout_id):
workout = get_object_or_404(Workout, workoutID = workout_id)
print(workout)
if request.user != workout.created_by:
return HttpResponse('Not ur workout')
else:
workout.delete()
return HttpResponseRedirect('/')
This is the url:
url(r'^(?P<workout_id>\d+)/delete/$', views.delete_workout, name='delete_workout'),
And finally the html:
<a href='{{ instance.delete }}'>
<button>Delete Workout</button>
</a>
I'm not getting any errors in the console, which is why I don't know what is going wrong.
You are overriding delete method of the class just for getting the delete url. You will get the url by url function in the template like {% url delete_workout instance.workoutID %}. So remove the delete function from the model change your html href url. Leave the view and url as the same. No issues there
class should be
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
And your html should be
<a href='{% url delete_workout instance.workoutID %}'>
<button>Delete Workout</button>
</a>
NOTE: django model itself adds id for each table, so you dont have to specify it as you did workoutID = models.AutoField(primary_key=True).
By default each model will have a id field just like id = models.AutoField(primary_key=True)
If you consider removing the workoutID then the model becomes
class Workout(models.Model):
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
and the html will be
<a href='{% url delete_workout instance.id %}'>
<button>Delete Workout</button>
</a>
Django has all the tools for you under the hood. Don't reinvent the wheel. You can refactor and simplify your code.
First remove the method delete in Workout.
Second, replace your function-based-view with a class-based-view:
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from django.http import Http404
from .models import Workout
class WorkoutDeleteView(DeleteView):
model = Workout
success_url = reverse_lazy('delete_workout')
def get_object(self):
obj = super().get_object()
if obj.created_by != self.request.user:
raise Http404
return obj
A workout can be deleted only by its author. In success_url you specify the target where the user should be redirected after deleting.
Just adapt slightly your urls.py (pay attention to the emphasised part):
url(r'^(?P<pk>\d+)/delete/$', views.WorkoutDeleteView.as_view(), name='delete_workout'),
EDIT:
You can name your views as you please, however it would be better to follow already well established conventions. Thus the names for the class based views should be workout-list, workout-detail, workout-create, workout-update and workout-delete.