Validate a field of form that allows multiple photos upload - django

I have a custom form that allows to upload multiple photos which I'm using in UserAdmin model. Also I have my own validator for a field in User model. I was trying to make a validator for the field of my form by overriding clean method, but clean method made my custom validator in User model unworkable, so it means my validator for a field in User model became useless because clean method in forms.py works for everything. I've tried to go through this answer but it didn't help me. How can I make each validator work for the field that they are intended for?
forms.py
from django import forms
from django.utils.safestring import mark_safe
from .models import UserImage
class PhotoUploadForm(forms.ModelForm):
photo = forms.FileField(
widget=forms.ClearableFileInput(attrs={'multiple': True}),
required=False,
help_text='Необходимо количество фото - 10',
label=mark_safe("<strong style='color:black'>Фото</strong>")
)
def clean_photos(self):
photos = self.files.getlist('photo')
if len(photos) != 10:
raise forms.ValidationError(
{'photo': f'Вы попытались загрузить {len(photos)} фотографий'})
return photos
class Meta:
model = UserImage
fields = '__all__'
admin.py
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
form = PhotoUploadForm
models.py
class User(models.Model):
first_name = models.CharField(
verbose_name='Имя',
max_length=40,
)
last_name = models.CharField(
verbose_name='Фамилия',
max_length=40
)
rfid_mark = models.CharField(
verbose_name='RFID',
max_length=10,
unique=True,
help_text='RFID должен состоять из 10 символов',
error_messages={
'unique': 'Такой RFID уже существует'
},
validators=[validate_rfid_length]
)

If the field you want to clean is called photo then the clean method you need is called clean_photo() not clean_photos()

Related

making an API that adds instances to ManyToMany fields of a model in django rest framework

I am making a movie watching website in which there are users and films and the user model has a ManyToMany Field that references the film model. it's called WatchList and an authenticated user can add any movie they want to this watchlist.
My problem is that I want an API that only gets the ID of a film and adds it to the user's watch list.
these are my models and serializers and I am trying to make a view to implement this API.
# models.py
class Film(models.Model):
filmID = models.AutoField(primary_key=True)
title = models.CharField(max_length=150)
# ...
class User(AbstractBaseUser, PermissionsMixin):
userID = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
watchList = models.ManyToManyField(Film)
objects = UserManager()
USERNAME_FIELD = 'username'
# serializers.py
class WatchListSerializer(serializers.ModelSerializer):
class FilmSerializer(serializers.ModelSerializer):
model = Film
fields = ('filmID', 'title',)
read_only_fields = ('filmID', 'title')
film_set = FilmSerializer(read_only=True, many=True)
class Meta:
model = get_user_model()
fields = ('userID', 'film_set')
read_only_fields = ('userID',)
# views.py
class WatchListAddView(...):
pass
The serializer can be changed. but this kind of shows what I want the api to be. the authentication validation part is already taken care of, so imagine that any request to the view is from an authenticated user.
I would not recommend patching this directly and instead create a separate endpoint for adding removing data to this field.
In your case it would look like this. I show just a small working example, you can adjust it to your needs
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
#action(detail=True,
methods=['POST'])
def add_film_to_watch_list(self, request, **kwargs):
film = get_object_or_404(klass=Film, filmID=kwargs.get('filmID'))
user = self.get_object()
user.watchList.add(film)
return Response("Success")

Django 1.9 forms include is not showing in admin

Im trying to change the view of admin though I try to modify admin with the help of forms, I dont see any changes and end up with all the fields
My model consisting primarily of email and name
class SignUp(models.Model):
email = models.EmailField()
full_name = models.CharField('name',max_length=120, blank=True, null=True,)
timestamp = models.DateTimeField('time',auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __unicode__(self): #Python 3.3 is __str__
return self.email
My form
class SignUpForm(forms.ModelForm):
class META:
model= SignUp
fields=['email']
Im trying to add the form in admin so that only email is displayed
My admin, I'm trying to include only email field
class SignUpAdmin(admin.ModelAdmin):
list_display = ['full_name', 'timestamp', 'updated']
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
But I end up with both email and name displayed. I only wanted email to be shown in admin page
Any help is much appriciated......Thanks in advance
I'm assuming the indentation on meta is a copy/paste error in your question, but that needs to be indented in your class, but also, it shouldn't all be capitalised
class SignUpForm(forms.ModelForm):
class Meta:
model= SignUp
fields=['email']

Combine two models with OneToOne relationship into one form - Django

I created two custom user models (AbstractBaseUser and a separate for extra information). Is there a way to combine the two models to create one form that the user can use to update all of their information between the two models?
For example, it would be ideal to have something like this (although I know not possible):
class ProfileChangeForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['username', 'email', first_name', 'last_name', 'bio', 'website']
Thank you in advance for your help! The models are below:
MyUser:
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=30, unique=True)
email = models.EmailField(max_length=255, unique=True)
first_name = models.CharField(max_length=120, null=True, blank=True)
last_name = models.CharField(max_length=120, null=True, blank=True)
UserProfile:
class UserProfile(models.Model):
user = models.OneToOneField(MyUser)
bio = models.TextField(null=True, blank=True)
website = models.CharField(max_length=120, null=True, blank=True)
Following solution worked for me. I used formsets to create this solution.
My models were as follows,
Models:
#Custom user model
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
#Related model(One-to-One relationship with custom user)
class Student(models.Model):
user = models.OneToOneField(CustomUser,on_delete = models.CASCADE)
first_name = models.CharField(max_length=50)
middle_name = models.CharField(max_length=50,blank=True,null=True)
last_name = models.CharField(max_length=50,blank=True,null=True)
After that I created two ModelForms
Forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser,Student
from django import forms
# Form for custom user
class SignUpForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('email', 'password1', 'password2')
class StudentCreationForm(forms.ModelForm):
class Meta:
model = Student
fields = ['user','first_name','middle_name','last_name']
Now the main part, I created a simple inline formset factory to handle Student model as an inline form.
Formset
from django.forms import inlineformset_factory
from .models import CustomUser,Student
from .forms import StudentCreationForm
# As parameters I provided parent Model(CustomUser),child Model(Student) and the Child
# ModelForm(StudentCreationForm)
StudentCreationFormSet = inlineformset_factory(CustomUser, Student,form=StudentCreationForm,extra=1,can_delete = False)
In views, I created the SignUpForm and StudentCreationFormSet object respectively. And in the POST request first I validated the CustomUser form and saved it without comitting it(commit=False). I created an object of custom user and passed it as a instance to the StudentCreationFormSet to validate the related form. If everything goes fine my both forms will be saved else the errors will be shown in the template.
View
from django.shortcuts import render,redirect
from .forms import SignUpForm
from .formsets import StudentCreationFormSet
def createAccountView(request):
student_account_form = SignUpForm()
student_details_formset = StudentCreationFormSet()
if request.method == 'POST':
student_account_form = SignUpForm(request.POST)
if student_account_form.is_valid():
# Validating Custom User form and creating object of it(not comitting as formset needed to be verified)
student_account = student_account_form.save(commit=False)
# Getting Custom User object as passing it as instance to formset
student_details_formset = StudentCreationFormSet (request.POST,instance=student_account)
if student_details_formset.is_valid():
student_account_form.save()
student_details_formset.save()
return redirect('login')
else:
student_details_formset = StudentCreationFormSet (request.POST)
context = {
'student_account_form':student_account_form,
'student_details_form':student_details_formset
}
return render(request, 'account/createStudentPage.html',context=context)
Also note that I am passing both the form and formset in single post request.
Template (createStudentPage.html)
<form method="POST" >
{% csrf_token %}
{{ student_account_form.as_p }}
{{ student_details_form.as_p }}
<button type="submit">Sign Up</button>
</form>
I think you can do something like :
class ProfileChangeForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['user__username', 'user__email', 'user__first_name', 'user__last_name', 'bio', 'website']

Connect one model to another

I'm trying to make a simple shop for myself without popular modules. And stack on next things.
I have two models - articles (kind of "product" in here) and user with custom profile model. So what I need is when User goes to Article page, he can push the button ("Buy" maybe) and that article model connects to User. So he can see it on his profile page. Also, I need a check function in template, indicates that User bought Article or not (some kind "if-else").
I'm already hooked up my Article model to my User Profile model with ForeignKey, but right now I don't know where the next point to move. Can someone help?
My model userprofile:
import PIL
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.db import models
from article.models import Article
class UserProfile(models.Model):
user = models.OneToOneField(User)
user_picture = models.ImageField(upload_to='users', blank=False, null=False, default='users/big-avatar.jpg')
user_balance = models.IntegerField(default=0)
user_articles = models.ForeignKey(Article, blank=True, null=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u) [0])
My forms.py for userprofile
from django import forms
from userprofile.models import User
from userprofile.models import UserProfile
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name',)
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('user_picture', 'user_balance')
My view for userprofile
from django.shortcuts import render, render_to_response, redirect
from django.shortcuts import HttpResponseRedirect, Http404, HttpResponse
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from userprofile.forms import User
from userprofile.forms import UserForm
from userprofile.forms import UserProfileForm
def userprofile(request, username):
u = User.objects.get(username=username)
if request.POST:
user_form = UserForm(request.POST, instance=request.user)
user_profile = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
if user_form.is_valid() and user_profile.is_valid():
user_form.save()
user_profile.save()
else:
user_form = UserForm(instance=request.user,
initial={
'first_name': request.user.first_name,
'last_name': request.user.last_name,
'email': request.user.email,
})
user = request.user
profile = user.profile
user_profile = UserProfileForm(instance=profile)
return render_to_response('profile.html', {'user_form': user_form, 'user_profile': user_profile}, context_instance=RequestContext(request))
And my model article, that needs to be hooked up:
import PIL
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.db import models
class Article(models.Model):
class Meta():
db_table = 'article'
article_title = models.CharField(max_length=200, blank=False, null=False)
article_anchor = models.CharField(max_length=200, blank=False, null=False)
article_image = models.ImageField(upload_to='items', blank=False, null=False)
article_users = models.IntegerField(default=0)
class Comments(models.Model):
class Meta():
db_table = 'comments'
comments_date = models.DateTimeField()
comments_text = models.TextField(verbose_name=u'')
comments_article = models.ForeignKey(Article)
comments_from = models.ForeignKey(User)
Just to clarify a few things:
I assume a user can purchase multiple articles
An article can belong to many users
If this is the case, then you have a many-to-many relationship between the user model and the article model. So what you can do is to modify your Article model:
class Article(models.Model):
class Meta():
db_table = 'article'
article_title = models.CharField(max_length=200, blank=False, null=False)
article_anchor = models.CharField(max_length=200, blank=False, null=False)
article_image = models.ImageField(upload_to='items', blank=False, null=False)
article_users = models.ManyToManyField(User) # <- use ManyToManyField instead of IntegerField
Another approach is to create a OrderHistory model to store who (User) purchased what(Article):
class OrderHistory(models.Model):
user = models.ForeignKey(User)
article = models.ForeignKey(Article)
purchase_date = models.DateTimeField(auto_now_add=True)
Let's assume that you used the first approach. Modifying models is not enough. There are a few things you need to add to your site:
A webpage for displaying a list of Articles for users to purchase
So you need a template file that shows a list of avaiable articles
you need a view function to render this page
this page will contain a list of articles and a way for users to select which article they want to buy via checkbox (or many buy buttons beside each article, your choice). So bascially your template will have a element that contains a list of articles and a 'buy' button to POST this data back to server
When a user clicks on the 'Buy' button, the data is submitted to a url that you need to define in the urls.py
add a new url in your urls.py and hook it to a view function
the view function will use request.user to identify which user it is and use the data in request.POST to figure out the article ids that's being purchased.
then you need to find the article from the database using
article = Article.objects.filter(pk=the_id_you_received_from_POST)
article.article_users.add(request.user)
article.save()
return a success message
Read this link before you start:
https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_many/
EDIT:
As Daniel pointed out, remove the following line from UserProfile
user_articles = models.ForeignKey(Article, blank=True, null=True)
You have to think about if the relationship between a user and an article is one-to-many or many-to-many. models.ForeignKey means one user can buy many articles, but once an article has been purchased, it can only belong to one user.(which is probably not what you want)
To pass data from a webpage to your view function, there are two ways:
Through GET request: parameters are appended to the end of the url, here is a good example of how it is done in Django: Capturing url parameters in request.GET
Through POST request: usually, you would have a form on the page and a submit button to submit the data to a predefined URL:
<form action = "url_for_handling_POST_request" method = "post">
Please follow Django's tutorial:
- https://docs.djangoproject.com/en/1.8/intro/tutorial04/
- https://docs.djangoproject.com/en/1.8/topics/forms/#processing-the-data-from-a-form
In your case, you should use POST request. so read the documentation above. It contains an example that matches to what you need.
Note: don't forget to insert the CSRF token inside your form or Django will complain:
<form ...>
{% csrf_token %}
</form>

Django User model fields at AdminModel

My purpose is to see at the admin site only user name, email and phone number.
I've create UserProfile by extending User model:
model.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
name = models.CharField(max_length=50, null=True,blank=True)
address = models.CharField(max_length=50, null=True,blank=True)
phone = models.CharField(max_length=20, null=True,blank=True)
country = models.CharField(max_length=20, null=True,blank=True)
state = models.CharField(max_length=20, null=True,blank=True)
zip = models.CharField(max_length=10, null=True,blank=True)
code = models.CharField(max_length=40, null=True)
def user_email(self):
return self.user.email
admin.py
from myApp.models import UserProfile
from django.contrib import admin
class UserProfileAdmin(admin.ModelAdmin):
fields = ('name','phone',)
list_display = ('name','user_email',)
admin.site.register(UserProfile, UserProfileAdmin)
so on the list_display it works, I can see only the columns I've chosen, but when I add 'user_email' ( fields = ('name','user_email', 'phone',) )to fields I get when I try to go to admin site:
'UserProfileAdmin.fields' refers to field 'user_email' that is missing from the form.
Fields on a related model use two underscores. Dunno if it'll work in the admin though.
list_display = ('name','user__email',)
Just because I recently used it and you maybe want this, too: If you wan't to add an inline admin to the "User" admin page in Django you can do this (at least in Django 1.3) by doing:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from models import UserProfile
class UserProfileInlineAdmin(admin.StackedInline):
model = UserProfile
class MyUserAdmin(UserAdmin):
inlines = [ UserProfileInlineAdmin ]
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
You can't put editable fields from a related model into an admin form, without using inlines. You can show the field as a readonly value: just add it to readonly_fields.