Django - Allow only one filed to fill in the form - django

I am using below django model for forms
class Post(models.Model):
author = models.ForeignKey(CustomUser,on_delete=models.CASCADE,)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
post_url = models.URLField(max_length = 200, blank = True)
picture = models.ImageField(upload_to='Image_folder', height_field=None, width_field=None, max_length=100, blank = True)
slug = models.SlugField(unique=True, blank=True)
I want to allow user to enter only one field either post_url or picture but not both. Can some give some an idea how to implement this?

# you can use clean or clean_yourfieldname function in your forms.py class
For Example
def clean(self):
cleaned_data = super().clean()
post_url = cleaned_data.get("post_url")
picture = cleaned_data.get("picture")
if post_url and picture:
raise forms.ValidationError(
"Please fill either post_url or picture field, but not both"
)
return cleaned_data

Related

ImageField is not updating when update() method is used in Django Serializer

I want to give a Admin the ability to update the image associated to a Product record. I have an edit class that allows the admin to update various elements of a record all of which are updating correctly except for the image fields. The problem is uploading image is working in Creating but not in Updating.
Model:
class Product(models.Model):
id = models.AutoField(primary_key=True, db_column='product_id')
user = models.ForeignKey(UserProfile, models.CASCADE, blank=True, null=True,db_column='user_id')
category = models.ForeignKey(Category, models.CASCADE, blank=True, null=True)
product_name = models.CharField(max_length=500, blank=True, null=True)
description = models.TextField(null=True, blank=True)
quantity = models.IntegerField(default=0, blank=True, null=True)
unit_price = models.FloatField(blank=True, null=True)
dis_price = models.FloatField(blank=True, null=True, db_column='discount_price')
available_qty = models.PositiveIntegerField(default=0,blank=True, null=True)
created_at = models.DateTimeField(default=datetime.datetime.now())
image1 = models.ImageField(db_column='product_image1', null=True, blank=True, upload_to='media/images/')
image2 = models.ImageField(db_column='product_image2', null=True, blank=True, upload_to='media/images/')
image3 = models.ImageField(db_column='product_image3', null=True, blank=True, upload_to='media/images/')
Serializer :
from .models import Product
#Products Adding Serilaizer
class ad_products(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['category','product_name','description','quantity','unit_price','dis_price','image1','image2','image3']
# Product Updating Serializer
class ad_products_update(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['category','product_name','description','unit_price','dis_price','image1','image2','image3']
Views :
POST
class Admin_products(CreateAPIView):
serializer_class = ad_products
#transaction.atomic
def post(self,request,token):
try:
token1 = KnoxAuthtoken.objects.get(token_key=token)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
user = token1.user_id
usertable = UserProfile.objects.get(id=user)
userdata = usertable.id
if(UserProfile.objects.filter(id=userdata, is_active='True')):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
productcategory = serializer.validated_data['category']
productname = serializer.validated_data['product_name']
description = serializer.validated_data['description']
quantity = serializer.validated_data['quantity']
unitprice = serializer.validated_data['unit_price']
discountprice = serializer.validated_data['dis_price']
image1 = serializer.validated_data['image1']
image2 = serializer.validated_data['image2']
image3 = serializer.validated_data['image3']
if(Category.objects.filter(category_name__iexact=productcategory)):
tablecategory = Category.objects.get(category_name__iexact=productcategory)
table = Product.objects.create(product_name=productname,
description=description, quantity=quantity, unit_price=unitprice,
dis_price=discountprice,user=usertable, category=tablecategory.id, image1=image1, image2=image2, image3=image3)
return Response("Success", status=status.HTTP_200_OK)
else:
data = {'message': "Category Not Found"}
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
data = {"message":'User is in In-Active, please Activate your account'}
return Response(data, status=status.HTTP_406_NOT_ACCEPTABLE)
Image is successfully uploaded to media folder and path is stored in Database.
PUT
class Admin_products_update(CreateAPIView):
serializer_class = ad_products_update
#transaction.atomic
def put(self,request,token,pid):
try:
token1 = KnoxAuthtoken.objects.get(token_key=token)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
user = token1.user_id
usertable = UserProfile.objects.get(id=user)
userdata = usertable.id
if(UserProfile.objects.filter(id=userdata, is_active='True')):
if(Product.objects.filter(user=userdata, id=pid)):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
category = serializer.validated_data['category']
productname = serializer.validated_data['product_name']
description = serializer.validated_data['description']
unitprice = serializer.validated_data['unit_price']
discountprice = serializer.validated_data['dis_price']
image1 = serializer.validated_data['image1']
image2 = serializer.validated_data['image2']
image3 = serializer.validated_data['image3']
if(Category.objects.filter(category_name__iexact=category)):
cat = Category.objects.get(category_name__iexact=category)
Product.objects.filter(id=pid, user=userdata).update(
category = cat.id, product_name=productname, description=description,
unit_price=unitprice, dis_price=discountprice,image1=image1, image2=image2, image3=image3)
data = {"message":'Product Details Updated successfully'}
return Response(data, status=status.HTTP_200_OK)
else:
data = {'message': "Category Not Found"}
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
data = {'message' : "Product Details Not Found"}
return Response(data, status=status.HTTP_404_NOT_FOUND)
else:
data = {"message":'Account is in In-Active, please Activate your account'}
return Response(data, status=status.HTTP_406_NOT_ACCEPTABLE)
When i use Update method image path is storing in DB, but Image is not storing in Media folder.
can anyone have an idea how to solve this issue
<queryset>.update(...) only adds kind of annotations to the queryset and affects only SQL query generation and does not call model.save method. But only model.save operates with model instances and calls field's save method which reaches file storage. A queryset (<model>.objects.filter().update()) cannot not do anything with file storages.
So instead of writing an update query you should instantiate model instance and save it. DRF documentation has examples of implementing serializers which save instances (as model instances, not direct DB update)
You use ModelSerializer which by default does call instance.save in update method implementation thus it is unclear how you got to your implementation. Just follow the docs and let model.save happen.

Why can not I submit a double form to the database

I created a form view and when I want to save a form with two modules I see "IntegrityError". Please help, Thank you in advance
class Place(models.Model):
LocalName = models.CharField('Nazwa Lokalu', max_length=200)
slug = models.SlugField('Link', max_length=100, default="")
LocalImg = models.ImageField('Zdjęcie Lokalu',
upload_to='Place/static/locals/img', blank=True, max_length=20000)
LocalAdress = models.CharField('Adres', max_length=500)
LocalNumberPhone = models.CharField('Numer telefonu', max_length=9)
LocalHours = models.TextField(verbose_name='Godziny otwarcia',
max_length=20000)
def get_aboslute_url(self):
return reverse("place:place_create", kwargs={'id': self.id})
class Meta:
verbose_name = "Lokal"
verbose_name_plural = "Lokale"
def __str__(self):
return self.LocalName
class Menu(models.Model):
place = models.ForeignKey(Place, on_delete=models.CASCADE,
related_name="place", default="")
Dinner = models.CharField("Nazwa potrawy",blank=True, default="",
max_length=200)
DinnerComponents = models.CharField("Składniki",blank=True, default="",
max_length=20009)
PizzaPrice = models.CharField("Rozmiar i cena Pizzy",
help_text="np.Mała-10zł", default="", blank=True, max_length=300)
Price = models.DecimalField("Cena",default="00", max_digits=5,
decimal_places=2)
class Meta:
verbose_name = "Menu"
verbose_name_plural = "Menu"
views.py
def create_place(request):
form = PlaceForm()
sub_form = MenuForm()
if request.POST:
form = PlaceForm(request.POST)
sub_form = MenuForm(request.POST)
if form.is_valid() and sub_form.is_valid():
place = form.save(commit=False)
place.location = sub_form.save()
place.save()
context = {
'form': form,
'sub_form': sub_form
}
return render(request, 'posts/layout.html', context)
Forms.py
class PlaceForm(forms.ModelForm):
class Meta:
model = Place
fields = ('LocalName', 'LocalAdress', 'LocalNumberPhone','LocalHours',)
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = ('Dinner','DinnerComponents','DinerCategory', 'Price',)
After filling in the form and clicking submit, an error will appear "NOT NULL constraint failed: posts_menu.place_id"
You have to first save a Place then assign the saved place to the Menu and finally save the menu.
if form.is_valid() and sub_form.is_valid():
place = form.save()
menu = sub_form.save(commit=False)
menu.place = place
menu.save()
That's because a Menu needs a place foreign key otherwise it cannot be saved.
(Note: why do you mix snake_case and CamelCase for your field names? It's terribly difficult to know how your model's properties are called. Python's convention is snake_case for all properties/methods/variables)

How to intercept and control saving a Django POST form?

When the user is required to fill his profile, he picks a city from the Google Places Autocomplete and posts the form, in the view I extract the city Id from the Google API based on the posted text (I use the same id as pk in my db) and try to extract a city from my db.
These are the models:
class City(models.Model):
#extracted from the Google API
city_id = models.CharField(primary_key=True, max_length=150)
name = models.CharField(max_length=128, blank=True)
country = models.CharField(max_length=128, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', primary_key=True)
city = models.ForeignKey(City, blank=True, null=True)
prof_pic = models.ImageField(blank=True, upload_to='profile_pictures')
This is the view:
def createprofile(request):
if request.method =='POST':
user = User.objects.get(username=request.user.username)
user_form = UserForm(data=request.POST, instance=user)
profile_form = UserProfileForm(data=request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save()
user.save()
profile = profile_form.save(commit=False)
profile.user = user
#brings back the city search result as text
searched_city = request.POST['city']
#brings back city ID from the Google API
searched_city_id = population_script.get_city_json(searched_city.replace(" ", ""))['results'][0]['id']
#If it's a valid city
if searched_city_id != -1:
city = City.objects.get(city_id = searched_city_id)
profile.city = city#this is what I want to happen!
else:
return HttpResponse("There's no such city, please try a different query.")
if 'prof_pic' in request.FILES:#now save the profile pic
profile.prof_pic = request.FILES['prof_pic']
print("PROF PIC IS: " + profile.prof_pic.url)
else:
profile.prof_pic = 'images/anon.png'
profile.save()
if 'next' in request.GET:
return redirect(request.GET['next'])
else:
print (user_form.errors, profile_form.errors)
else:
user_form = UserForm()
profile_form = UserProfileForm()
return render(request,
'excurj/createprofile.html', {'user_form':user_form, 'profile_form':profile_form})
However, I keep receiving an error that what's been posted is just text while the city needs to be a City object. I can save the profile pic ok though.
Cannot assign "'Dubai - United Arab Emirates'": "UserProfile.city"
must be a "City" instance.
edit: these are the forms:
class UserForm(forms.ModelForm):
first_name = forms.CharField(
label = "First Name:",
max_length = 80,
required = True
)
last_name = forms.CharField(
label = "Last Name:",
max_length = 80,
required = True,
)
class Meta:
model = User
fields = ('first_name', 'last_name')
class UserProfileForm(forms.ModelForm):
city = forms.CharField(
label = "Your Current City:",
max_length = 200,
required = True,
)
class Meta:
model = UserProfile
fields = ('city','prof_pic', 'dob', 'sex', 'education', 'career', 'about_you',
'music_movies_books', )
Please provide a related_name to the city field in the UserProfile.
I worked around this by creating a new UserProfile field called city_search_text which saves the searched text thus it of course does not return any error. I then receive it in the POST request and comfortable pull the proper city in the view.
I handled a similar issue by overriding my forms' clean method. Something like the following will work:
def clean(self):
# fix city problem
if self.cleaned_data.get("city") is not None:
self.cleaned_data['city'] = City.objects.get(id=self.cleaned_data.get("city"))
return self.cleaned_data

Django, want to upload either image (ImageField) or file (FileField)

I have a form in my html page, that prompts user to upload File or Image to the server. I want to be able to upload ether file or image. Let's say if user choose file, image should be null, and vice verso. Right now I can only upload both of them, without error. But If I choose to upload only one of them (let's say I choose image) I will get an error:
"Key 'attachment' not found in <MultiValueDict: {u'image': [<InMemoryUploadedFile: police.jpg (image/jpeg)>]}>"
models.py:
#Description of the file
class FileDescription(models.Model):
TYPE_CHOICES = (
('homework', 'Homework'),
('class', 'Class Papers'),
('random', 'Random Papers')
)
subject = models.ForeignKey('Subjects', null=True, blank=True)
subject_name = models.CharField(max_length=100, unique=False)
category = models.CharField(max_length=100, unique=False, blank=True, null=True)
file_type = models.CharField(max_length=100, choices=TYPE_CHOICES, unique=False)
file_uploaded_by = models.CharField(max_length=100, unique=False)
file_name = models.CharField(max_length=100, unique=False)
file_description = models.TextField(unique=False, blank=True, null=True)
file_creation_time = models.DateTimeField(editable=False)
file_modified_time = models.DateTimeField()
attachment = models.FileField(upload_to='files', blank=True, null=True, max_length=255)
image = models.ImageField(upload_to='files', blank=True, null=True, max_length=255)
def __unicode__(self):
return u'%s' % (self.file_name)
def get_fields(self):
return [(field, field.value_to_string(self)) for field in FileDescription._meta.fields]
def filename(self):
return os.path.basename(self.image.name)
def category_update(self):
category = self.file_name
return category
def save(self, *args, **kwargs):
if self.category is None:
self.category = FileDescription.category_update(self)
for field in self._meta.fields:
if field.name == 'image' or field.name == 'attachment':
field.upload_to = 'files/%s/%s/' % (self.file_uploaded_by, self.file_type)
if not self.id:
self.file_creation_time = datetime.now()
self.file_modified_time = datetime.now()
super(FileDescription, self).save(*args, **kwargs)
forms.py
class ContentForm(forms.ModelForm):
file_name =forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':20}))
file_description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':25}))
class Meta:
model = FileDescription
exclude = ('subject',
'subject_name',
'file_uploaded_by',
'file_creation_time',
'file_modified_time',
'vote')
def clean_file_name(self):
name = self.cleaned_data['file_name']
# check the length of the file name
if len(name) < 2:
raise forms.ValidationError('File name is too short')
# check if file with same name is already exists
if FileDescription.objects.filter(file_name = name).exists():
raise forms.ValidationError('File with this name already exists')
else:
return name
views.py
if request.method == "POST":
if "upload-b" in request.POST:
form = ContentForm(request.POST, request.FILES, instance=subject_id)
if form.is_valid(): # need to add some clean functions
# handle_uploaded_file(request.FILES['attachment'],
# request.user.username,
# request.POST['file_type'])
form.save()
up_f = FileDescription.objects.get_or_create(
subject=subject_id,
subject_name=subject_name,
category = request.POST['category'],
file_type=request.POST['file_type'],
file_uploaded_by = username,
file_name=form.cleaned_data['file_name'],
file_description=request.POST['file_description'],
image = request.FILES['image'],
attachment = request.FILES['attachment'],
)
return HttpResponseRedirect(".")
Let's say if user choose file, image should be null, and vice verso.
You could:
make an SQL constraint,
override model.save() to fail if either file or image is blank,
define ContentForm.clean() to raise a ValidationError if either file or image is blank, see Cleaning and validating fields that depend on each other.
Also be careful that up_f will be a tuple in:
up_f = FileDescription.objects.get_or_create(
I've had the same problem. For some reason the key value dict only takes one key pair values. save the the attachment like so
attachment=form.data['attachment']
as opposed to
attachment=request.FILES['attachment']
it should run, but curious if it will save as a file.
i know this question is old but had a difficult time with this same issue
Create radio buttons for the user to chose what he/she want to upload and use only one FileField attribute in the model. You can convert the other field to BooleanField or CharField to indicate what the user selected.

Contextual form validation in Django

I want to do "contextal" form validation in django. Consider this case:
PLACE_TYPES = (
('RESTAURANT', 'Restaurant'),
('BARCLUB', 'Bar / Club'),
('SHOPPING', 'Shopping'),
)
RESTAURANT_FORMAT_CHOICES = (
('FAST_FOOD', 'Fast Food'),
('FAST_CASUAL', 'Fast Casual'),
('CASUAL', 'Casual'),
('CHEF_DRIVEN', 'Chef Driven'),
)
class Place(models.Model):
place_type = models.CharField(max_length=48, choices=PLACE_TYPES, blank=False, null=False)
name = models.CharField(max_length=256)
website_1 = models.URLField(max_length=512, blank=True)
hours = models.CharField(max_length=1024, blank=True)
geometry = models.PointField(srid=4326, blank=True, null=True)
#Restaurant Specific
restaurant_format = models.CharField(max_length=128, choices=RESTAURANT_FORMAT_CHOICES, blank=True, null=True)
So in the django admin, the corresponding form for Place will have pulldown menu with choices like "restaurant, bar, club", and there is another field called "restaurant_format".
Validation should make sure restaurant_field cannot be null if the first pulldown was set as "restaurant".
I am trying something like this:
class PlaceAdminForm(forms.ModelForm):
def clean(self):
if self.cleaned_data['place_type'] == 'RESTAURANT':
if self.cleaned_data['place_type'] is None:
raise forms.ValidationError('For a restaurant you must choose a restaurant format')
but get this error:
Exception Type: KeyError
Exception Value:
place_type
Exception Location: /place/admin.py in clean, line 27
i think i got it working with this clean routine:
def clean(self):
cleaned_data = self.cleaned_data
place_type = cleaned_data.get("place_type")
restaurant_format = cleaned_data.get("restaurant_format")
if place_type == 'RESTAURANT':
if self.cleaned_data['restaurant_format'] is None:
raise forms.ValidationError('For a restaurant you must choose a restaurant format')
# Always return the full collection of cleaned data.
return cleaned_data