I have a post and a category app in my django project. I have user authentication and users can create categories as well as posts. I am at a point where I am out of ideas and help would be appreciated. I want users to be able to reference only their own category and create posts in their categories not in another persons own. That is if a user creates more that one category he should be able to select from the list of his created category and not see another persons own.
category model
class Category(models.Model):
name = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
post model
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,related_name='posts_created') #blank=True, null=True)
title = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category_created', null= True)
addition codes would be provided immediately on request. Thanks
View.py in post app
def create(request):
if not request.user.is_authenticated():
messages.error(request, "Kindly confirm Your mail")
#or raise Http404
form = PostForm(request.POST or None, request.FILES or None)
user = request.user
categories = Category.objects.filter(category_created__user=user).distinct()
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
create_action(request.user, 'Posts', instance)
messages.success(request, "Post created")
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"form": form,
"categories": categories,
}
template = 'create.html'
return render(request,template,context)
Form
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [
"title",
"content",
"category",
]
html
{% if form %}
<form method="POST" action="" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy|safe }}
<input type="submit" name="submit" value="Publish">
</form>
{% endif %}
You probably want to list all the Categories to which a user has contributed on some view.
You can get all the Categories to which a user contributed in the following way:
user = request.user # assuming you're in a view
categories = Category.objects.filter(post__user=user).distinct()
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category_created', null= True)
related_name='category_created' means that to access your post from a category you need to use 'category_created':
categories = Category.objects.filter(category_created__user=user).distinct()
or you can rename it to related_name='post' and then migrate.
Edit
View.py in post app
def create(request):
if not request.user.is_authenticated():
messages.error(request, "Kindly confirm Your mail")
#or raise Http404
form = PostForm(request, request.POST or None, request.FILES or None, )
user = request.user
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
create_action(request.user, 'Posts', instance)
messages.success(request, "Post created")
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"form": form,
"categories": categories,
}
template = 'create.html'
return render(request,template,context)
forms.py
class PostForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
super (PostForm,self ).__init__(*args,**kwargs) # populates the post
self.fields['category'].queryset = Category.objects.filter(
category_created__user=request.user
).distinct()
Related
The idea is a user will create a post on this page and fill out the title and content.
Once they submit this form, a Post instance will be created with the inputs of the user for title and content. This instance will also have the id of the user associated to it.
I've done a print test which yields the outputs I'm looking for but it appears that the form is not valid. Any thoughts?
#Resolved - Thank you all
def post(self, request, locationname):
form = PostCreate(request.POST)
current_user = request.user
found_location = Location.objects.get(name = locationname)
if form.is_valid():
form = form.save(commit=False)
form.post_location = Location.objects.get(id = found_location.id)
form.author = Profile.objects.get(user_id = current_user)
form.save()
return HttpResponseRedirect(self.request.path_info)
#views.py
class LocationDetail(View):
def post(self, request, locationname):
current_user = request.user
user_profile = Profile.objects.get(user_id = current_user)
form = PostCreate()
found_location = Location.objects.get(name = locationname)
context = {"found_location": found_location, "form": form}
if request.method == "POST":
post_author = user_profile.user_id
post_title = request.POST['title']
post_content = request.POST['content']
print(post_author)
print(post_title)
print(post_content)
if form.is_valid():
Post.objects.create(
title = post_title,
content = post_content,
author_id = post_author,
)
form.save()
return render(request,'location-detail.html', context)
#forms.py + models.py
class PostCreate(ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
class Profile(Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length = 30)
profile_location = models.CharField(max_length = 80, blank=True, null=True)
join_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Post(Model):
author = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="posts")
title = models.CharField(max_length = 30)
content = models.CharField(max_length=300)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.author} - {self.title}'
#template: location-detail.html
<form action="" method="POST">
{%csrf_token%}
{{form}}
<button type="submit">Submit</button>
</form>
You do not need to check if the request is a "POST" request since you are using class-based views. The post() method will be called by the dispatcher method inside of the base "View" class when you submit the form. Before checking if the form is valid, fill the instance of the form with the request data, then you can set the "author" field like so:
def post(self, request, locationname):
form = PostCreate(request.POST)
if form.is_valid():
# don't commit to database yet
form = form.save(commit=False)
# set author field
form.author = user_profile.user_id
# commit to database
form.save()
return redirect("<url_name>")
When .save(commit=False) is called, the form instance with the data is saved without committing to the database. Then, when the .save() method is called the second time, the data will be saved to the database. It is recommended to redirect to the desired url after a POST request. The <url_name> comes from the url in your 'urls.py' file that you want to redirect to.
More info can be found in the django docs here:
https://docs.djangoproject.com/en/3.2/topics/forms/
Try to revise this line:
user_profile = Profile.objects.get(user_id = current_user)
to
user_profile = Profile.objects.get(user_id = current_user.id)
Since the argument user_id needs an id value, not an object.
I created a sign up form using two grouped forms and it has been working perfectly, but I would like to use django-allauth because of the features (login only with e-mail, sending confirmation e-mail ...).
However even reading some topics I still couldn't.
forms.py
class ExtendedUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True, label="E-mail")
first_name = forms.CharField(max_length=30, label="Nome")
last_name = forms.CharField(max_length=30, label="Sobrenome")
class Meta:
model = User
fields = ('first_name', 'last_name', 'username', 'email', 'password1', 'password2')
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
if commit:
user.save()
return user
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('sexo', 'data_nascimento', 'foto', 'sobre_mim', 'telefone', 'paroquia',
'cidade','estado', 'cep', 'possui_filhos', 'facebook', 'instagram')
CIDADES = []
for i in cidadesReader:
if i[1] not in CIDADES:
CIDADES.append(i[1])
widgets = {
'cidade': floppyforms.widgets.Input(datalist=CIDADES, attrs={'autocomplete': 'off'}),
}
views.py
def signup(request):
if request.method == 'POST':
form = ExtendedUserCreationForm(request.POST)
profile_form = UserProfileForm(request.POST, request.FILES)
if form.is_valid() and profile_form.is_valid():
user = form.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=password)
#login(request, user)
return redirect('home')
else:
form = ExtendedUserCreationForm()
profile_form = UserProfileForm()
context = {'form': form, 'profile_form' : profile_form}
return render(request, 'registration/signup.html', context)
signup.html
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Cadastrar{% endblock title %}
{% block content %}
<h2>Criar Perfil</h2>
<form novalidate method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ profile_form|crispy }}
<button class="btn btn-success" type="submit">Cadastrar</button>
</form>
{% endblock content %}
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
SEXOS = (
('M', 'Masculino'),
('F', 'Feminino'),
)
sexo = models.CharField(max_length=1, choices=SEXOS)
data_nascimento = models.DateField(validators=[idade_minima])
...
I've tried using the ACCOUNT_SIGNUP_FORM_CLASS and ACCOUNT_FORMS options in settings.py, but it didn't work.
I tried to make some adjustments, as in this topic similar to my question:
Django allauth saving custom user profile fields with signup form
For example, I changed it in models.py and I did migrate:
user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, related_name ='profile')
After several attempts, the most common error is:
RelatedObjectDoesNotExist at /accounts/signup/
User has no profile.
Edit:
I changed my slug in UserProfile, because it depends from user (first name). The error changed:
IntegrityError at /accounts/signup/
NOT NULL constraint failed: profiles_userprofile.user_id
But UserProfile has no user continues in the final.
(Using in settings.py: ACCOUNT_SIGNUP_FORM_CLASS = 'profiles.forms.UserProfileForm'. Details from traceback:
...lib/python3.6/site-packages/allauth/account/views.py in dispatch
215 return super(SignupView, self).dispatch(request, *args, **kwargs)
.../lib/python3.6/site-packages/allauth/account/views.py in post
104 response = self.form_valid(form)
...lib/python3.6/site-packages/allauth/account/views.py in form_valid
231 self.user = form.save(self.request)
...lib/python3.6/site-packages/allauth/account/forms.py in save
405 self.custom_signup(request, user)
...lib/python3.6/site-packages/allauth/account/forms.py in custom_signup
359 custom_form.save(user)
...profiles/models.py in save
super(UserProfile, self).save(*args, **kwargs)
▼ Local vars
Variable Value
__class__
<class 'profiles.models.UserProfile'>
args ()
kwargs {}
self Error in formatting: RelatedObjectDoesNotExist: UserProfile has no user.
slug_name 'nome-sp-260221205510'
Signals
Using signals the error changed. I added it in models.py:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
Error:
ValueError at /accounts/signup/
The 'foto' attribute has no file associated with it.
Then I tried remove foto field, but the other error happens in another field:
IntegrityError at /accounts/signup/
NOT NULL constraint failed: profiles_userprofile.data_nascimento
Thanks in advance for any help.
The error UserProfile has no user is triggered in UserProfile.save(). You are calling this the first time in your view with commit=False and only afterwards are you setting the user:
# your code from the question
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
I'm guessing that UserProfile.save reads the the user field to create the slug. You could either skip it if commit=False, or maybe it will already work if you change it like this:
profile_form.instance.user = user
profile.save()
Another common solution is, to provide the user when initializing the form, but then you would have to change your current view code quit a bit.
I achieved! No need to use signals. Here are the changes:
forms.py
I needed to use a single class:
class SignupForm(forms.ModelForm):
first_name = forms.CharField(max_length=30, label="Nome")
last_name = forms.CharField(max_length=30, label="Sobrenome")
class Meta:
model = UserProfile
fields = ('sexo', 'data_nascimento', 'foto', 'sobre_mim','telefone','paroquia',
'cidade','estado', 'cep', 'possui_filhos', 'facebook', 'instagram')
CIDADES = []
for i in cidadesReader:
if i[1] not in CIDADES:
CIDADES.append(i[1])
widgets = {
'cidade': floppyforms.widgets.Input(datalist=CIDADES, attrs={'autocomplete': 'off'}),
}
field_order = ['first_name', 'last_name', 'email', 'password1', 'password2',
'sexo', 'data_nascimento', 'foto', 'sobre_mim','telefone','paroquia',
'cidade','estado', 'cep', 'possui_filhos', 'facebook', 'instagram']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
profile, created = models.UserProfile.objects.get_or_create(user=user)
profile.sexo = self.cleaned_data['sexo']
profile.data_nascimento = self.cleaned_data['data_nascimento']
def compressImage(foto):
...
return foto
profile.foto = compressImage (self.cleaned_data['foto'])
profile.sobre_mim = self.cleaned_data['sobre_mim']
profile.telefone = self.cleaned_data['telefone']
profile.paroquia = self.cleaned_data['paroquia']
profile.cidade = self.cleaned_data['cidade']
profile.estado = self.cleaned_data['estado']
profile.cep = self.cleaned_data['cep']
profile.possui_filhos = self.cleaned_data['possui_filhos']
profile.facebook = self.cleaned_data['facebook']
profile.instagram = self.cleaned_data['instagram']
user.save()
profile.save()
Note:
I was using a function to compress images, in models.py.
To correct the error
ValueError at /accounts/signup/
The 'foto' attribute has no file associated with it
I had to bring it to forms.py
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'profiles.forms.SignupForm'
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, related_name ='profile')
SEXOS = (
('M', 'Masculino'),
('F', 'Feminino'),
)
sexo = models.CharField(max_length=1, choices=SEXOS)
...
Note:
It was necessary to test field by field.
Sometimes there were some errors like NOT NULL constraint failed ou no such table.
The solutions to these problems:
Add null=True in the field (temporarily)
makemigrations and migrate
Delete migrations
signup.html
Only {{ form|crispy }} is necessary (I could delete {{ profile_form|crispy }})
<form novalidate method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Cadastrar</button>
</form>
Thank you for your help, #Risadinha.
I have a model as follow:
class Post(models.Model):
title = models.CharField(max_length=150)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
imagefile = models.FileField(null=True, blank=True, upload_to='images', default='default.jpg')
There is a bug in the view file that, when I use class base view I can post the new post but whenever I use it as a function it does not save the form.
This one works(saves post in database):
class PostCreateView(LoginRequiredMixin, AjaxFormMixin, CreateView):
model = Post
template_name = "app/postform.html"
success_url = '/posts'
form_class = postform
But this one does not(does not save post in database):
#login_required
def PostCreateView(request):
if request.method == 'POST':
mform = postform(request.POST or None, request.FILES or None, instance=request.user)
if mform.is_valid():
mform.save() # when I print something here, Ill see something
messages.success(request, f'Yes!')
return redirect('posts')
else:
mform = postform()
return render(request, 'myapplication/postform.html', {'form': mform})
and in postform.html:
<form method="POST" action="" enctype="multipart/form-data">
<fieldset class="form-group">
{% csrf_token %}
<div class="content-section">
<!-- {{ form|crispy }} -->
</div>
</fieldset>
</form>
and form.py:
class postform(forms.ModelForm):
class Meta:
model = Post
fields = ("__all__")
exclude = ['date_posted']
I think the problem is that you form's model is post and you're assigning object of user as instance.
So try this way:
#login_required
def PostCreateView(request):
if request.method == 'POST':
mform = postform(request.POST or None, request.FILES or None)
if mform.is_valid():
post = mform.save(commit=False)
post.author = request.user
post.save()
messages.success(request, f'Yes!')
return redirect('posts')
else:
mform = postform()
return render(request, 'myapplication/postform.html', {'form': mform})
I keep getting this error while trying to submit my django form. I'm quite new to django and this error has got me stuck for days.
here's my model.py
class Person(models.Model):
name = models.OneToOneField(User, on_delete=models.CASCADE, unique=True)
choices = (
('Staff', 'staff'),
('Building Manager', 'BM'),
)
type = models.CharField(max_length=30, choices=choices)
def __str__(self):
return '{0} {1} {2}'.format(self.pk, self.name.first_name, self.name.last_name)
class Building(models.Model):
address = models.CharField(max_length=100)
person = models.ForeignKey(Person, on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return self.address
class Device(models.Model):
name = models.CharField(max_length=30)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
total = models.IntegerField()
building = models.ForeignKey(Building, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.name
class Ticket(models.Model):
name = models.ForeignKey(Person, on_delete=models.CASCADE)
issue = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
device = models.ForeignKey(Device, on_delete=models.CASCADE)
room = models.CharField(max_length=255)
urls.py
urlpatterns = [
path('', views.home, name='home'),
path('ajax/load-devices/', views.load_device, name='ajax_load_device')
]
views.py
def home(request, cls=TicketForm):
items = Building.objects.all()
if request.method == "POST":
form = cls(request.POST or None)
if form.is_valid():
form.save()
return redirect(reverse('home'))
else:
form = cls()
return render(request, 'home.html', {'form': form, 'items': items, 'header': 'Building'})
def load_device(request):
category_id = request.GET.get('category')
devices = Device.objects.filter(category_id=category_id).order_by('name')
return render(request, 'device_dropdown.html', {'devices': devices})
forms.py
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = ['name', 'issue', 'category', 'device', 'room']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['device'].queryset = Device.objects.none()
if 'country' in self.data:
try:
category_id = int(self.data.get('category'))
self.fields['device'].queryset = Device.objects.filter(category_id=category_id).order_by('name')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty Device queryset
elif self.instance.pk:
self.fields['device'].queryset = self.instance.category.device_set.order_by('name')
home.html
<form class="forms-sample" action="" id="TicketForm" method="post" data-device-url="{% url 'ajax_load_device' %}">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="id_{{ field.name }}">{{ field.label }}</label>
<div>{{ field }}</div>
</div>
{% endfor %}
<button type="submit" value="Submit" class="btn btn-success mr-2">Send</button>
</form>
Then i tried moving the return statement in the views.py out of the else and the form wasnt saving. Please I've battled with this for a long time now and id appreciate if someone helped. Thanks
-I would move the cls parameter out of the home function definition in views.py and import at top
-then next i would remove the or None out of defining the Ticket form like
-I imagine your form is not validating, which is not a handled in a else statement causing your view to not return a response object
see below:
from .forms import TicketForm
def home(request):
items = Building.objects.all()
if request.method == "POST":
form = TicketForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('home'))
else:
return HttpResponse("Form Failed to Validate")
else:
form = cls()
return render(request, 'home.html', {'form': form, 'items': items, 'header': 'Building'})
if your request is a POST, but form.is_valid() returns false, you drop out of everything, and the function will (implicitly) return None.
I bet if you outdent that last return render(...) you will get what you want.
I want to fetch each and every detail of user profile. I know function get_profile() has been depreciated.
I want get user profile and then pass it as context to template.
Actually I am making a "Edit user profile" functionality.
My Model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
state = models.CharField(max_length=200, blank=True)
country = models.CharField(max_length=200, blank=True)
zipcode = models.CharField(max_length=200, blank=True)
And views:
#login_required
def dashboard(request):
context = RequestContext(request)
profile = request.user.userprofile
context_dict = {'profile': profile}
return render_to_response('appname/dashboard.html', context_dict, context)
To edit both User and Profile instance you have to use two forms:
class UserForm(forms.ModelForm):
class Meta:
class = User
fields = ('username', 'first_name', 'last_name')
class ProfileForm(forms.ModelForm):
class Meta:
class = UserProfile
exclude = ('user', )
#login_required
def edit_profile(request):
user = request.user
profile = user.userprofile
if request.method == 'POST':
user_form = UserForm(request.POST, instance=user)
profile_form = ProfileForm(request.POST, instance=profile)
if all([user_form.is_valid(), profile_form.is_valid()]):
user_form.save()
profile_form.save()
return redirect('.')
else:
user_form = UserForm(instance=user)
profile_form = ProfileForm(instance=profile)
return render(request, 'user_profile.html',
{'user_form': user_form, 'profile_form': profile_form})
And the template should contain both forms in the single <form> tag:
<form action="." method="POST">
{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<button>Update</button>
</form>