I am trying to create an update form from User.profile. But when i load the url i get an error:
IntegrityError at /accounts/profile/
userprofile_userprofile.dob may not be NULL
Even if I give the 'dob' as null=True, I am getting the same error. But from the admin, I can add UserProfiles. Where am I doing wrong? Please help.
models.py:
from django.db import models
from django.contrib.auth.models import User
from time import time
def get_upload_file_name(instance, filename):
return "uploaded_files/profile/%s_%s" %(str(time()).replace('.','_'), filename)
class UserProfile(models.Model):
GENDER = (
("M", "Male"),
("F", "Female"),
("O", "Other"),
)
RELATIONSHIP = (
("S", "Single"),
("M", "Married"),
("D", "Divorced"),
)
user = models.OneToOneField(User)
image = models.ImageField(upload_to=get_upload_file_name)
gender = models.CharField(max_length=1, choices=GENDER)
dob = models.DateField('date of birth', blank=True, null=True)
about = models.TextField()
occupation = models.CharField(max_length=30)
state = models.CharField(max_length=20)
t_address = models.CharField(max_length=30)
p_address = models.CharField(max_length=30)
relationship = models.CharField(max_length=1, choices=RELATIONSHIP)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
forms.py:
from django import forms
from models import UserProfile
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('image', 'gender', 'dob', 'about', 'state', 't_address', 'p_address', 'relationship')
You have likely already run syncdb prior to making your edits (see karthikr's comment). You can either delete the table and rerun syncdb or you can edit the table using SQL:
ALTER TABLE user_profile ALTER COLUMN dob DROP NOT NULL;
I don't think you are able to update existing tables with syncdb
In order for this to work you should either manually edit the database using SQL or whatever GUI you have available or you can drop the table(s) that you want to edit, update your models.py with null=True/blank=Trueand then run syncdb.
Related
I would like to execute a single query in Django which retrieves related data, by foreign key, in multiple tables. At present I have to run a query on each table e.g. (House, Furniture, People) using the House number as a filter.
In SQL I can do this in one query like this:
SELECT house.number, house.number_of_rooms, furniture.type, people.name
FROM (house INNER JOIN furniture ON house.number = furniture.house_number)
INNER JOIN people ON house.number = people.house_number
WHERE (((house.number)="21"));
Can this be done in Django?
See example models below:
class House(models.Model):
number = models.CharField('House Number', max_length=10, blank=True, unique=True, primary_key=True)
number_of_rooms = models.IntegerField(default=1, null=True)
class Furniture(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
In your models add related_name arguments for foreign keys, so that you can retrieve the objects related to the House() instance.
class Furniture(models.Model):
house_number = models.ForeignKey(House, related_name='house_furniture', on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, related_name='house_people', on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
Then run the migration using following commands.
python manage.py makemigrations
python manage.py migrate
Then create a new serializers.py module in the same app.
#import models Furniture, People, house
from rest_framework import serializers
class FurnitureSerializer(serializer.ModelSerializer):
class Meta:
model = Furniture
fields = ['type'] # if you want all the fields of model than user '__all__'.
class PeopleSerializer(serializer.ModelSerializer):
class Meta:
model = People
fields = ['first_name'] # if you want all the fields of model than user '__all__'.
class HouseSerializer(serializer.ModelSerializer):
house_furniture = FurnitureSerializer(many=True)
house_people = PeopleSerializer(many=True)
class Meta:
model = Furniture
fields = ['number', 'number_of_rooms', 'house_furniture', 'house_people']
Now, in your views.py you can simply query on model House and serializer the result with HouseSerializer().
#import models from models.py
#import serializer from serializers.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import ListAPIView
class ListHouseView(ListAPIView):
serializer_class = HouseSerializer
queryset = House.objects.filter() #here you can apply filters on the fields of house model and user using related_name you can filter on other related models as well.
Now, simply call ad this in your app's urls.py
url_pattern = [
path('list-house/', ListHouseView.as_view()),
]
Make sure that have a path in your project's urls.py to reach this app's urls.py.
The usual Django way of dealing with this is Queryset.prefetch_related() and iterating through Python (unless you're using Postgres, which has its own solution of ArrayAgg). Given your models, it'll cost three queries, but you won't have to deal with de-normalized row results.
h = House.objects.prefetch_related('furniture_set', 'people_set').get(number='21')
for furniture in house.furniture_set.all():
print(furniture)
for person in house.people_set.all():
print(people)
prefetch_related() caches the results and does the "joining" in Python once the queryset is evaluated, so iterating through the reverse relationships won't incur additional queries, and you're free to structure/serialize the data however you like. The raw SQL from this is something like:
SELECT house.number, house.number_of_rooms FROM house WHERE house.number = '1'
SELECT furniture.id, furniture.house_number_id, furniture.type FROM furniture WHERE furniture.house_number_id IN ('1')
SELECT people.id, people.house_number_id, people.first_name FROM people WHERE people.house_number_id IN ('1')
But Django does that behind-the-scenes so that you can just deal with a model instance in Python.
I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).
I want to be able to search all models for all fields in Django admin, without having to setup ModelAdmin and searchfields individually.
example:
I have all my models in model.py:
# This is an auto-generated Django model module.
from django.db import models
class Diagnosis(models.Model):
id = models.BigAutoField(primary_key=True)
code = models.CharField(max_length=255)
starting_node = models.ForeignKey('Node', models.DO_NOTHING, blank=True, null=True)
class Meta:
managed = False
db_table = 'diagnosis'
def __str__(self):
return 'Diag #' + str(self.id) + ' - ' + self.code
class DiagnosisHistory(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=255, blank=True, null=True)
date = models.DateTimeField(blank=True, null=True)
id_user = models.TextField(blank=True, null=True)
report = models.TextField(blank=True, null=True)
json_report = models.TextField(blank=True, null=True)
vin = models.CharField(max_length=20, blank=True, null=True)
class Meta:
managed = False
db_table = 'diagnosis_history'
# and so on
and the admin.py where I register the models:
from django.contrib import admin
from . import models
# Do not care. Register everything
for cls in [cls for name, cls in models.__dict__.items() if isinstance(cls, type)]:
admin.site.register(cls)
I don't want to run through each Model and manually create a ModelAdmin with each field
This is the solution I came up with:
from django.contrib import admin
from django.db import models as django_models
from . import models
relationship_fields = (django_models.ManyToManyField, django_models.ForeignKey, django_models.OneToOneField)
for cls in [cls for name, cls in models.__dict__.items() if isinstance(cls, type)]:
meta_fields = [field.name for field in cls._meta.local_fields if not isinstance(field, relationship_fields)]
class Admin(admin.ModelAdmin):
search_fields = meta_fields
admin.site.register(cls, Admin)
Note: registering all fields will fail since some are relationships. using cls._meta.local_fields exclude inferred relationships but you also need to exclude fields such as foreign keys defined in your model. Thus, we filter with isinstance(field, relationship_fields)
Note 2: I should probably use get_fields since local_fields seems to be private API (see https://docs.djangoproject.com/en/3.1/ref/models/meta/)
class MyModelAdmin(admin.ModelAdmin):
# ...
search_fields = [field.name for field in MyModel._meta.fields]
# ...
I have an IntegerField in my model and I try to update it in the admin page. No matter what value I input (e.g. 100, 200), the value is restored to 0 after saving it.
models.py
class inputform(models.Model):
name = models.CharField('Name', max_length=40)
gender_choices = (
(0,'Female'), (1,'Male'),
)
gender = models.SmallIntegerField('Gender', choices=gender_choices, blank=True)
age = models.IntegerField('Age', blank=True, null=True)
email = models.CharField('email', max_length=64, blank=True)
live_choices = (
(0,'Other'), (1,'Live alone'), (2,'Live with family'),
)
live = models.SmallIntegerField('Living Status', choices=live_choices, blank=True)
admin.py
from django.contrib import admin
from myApp.models import inputForm
class inputForm(admin.ModelAdmin):
list_display = ('id','name', 'age', 'gender')
I have tried change the value via SQL in the database directly and it updated successfully. However, when I read it in the admin page and saved it without any change, the value restore to 0.
Is there any wrong with my code? Thanks!
I am new to django and am trying to set up a simple employee timesheet site that has multiple users. I set up two models one for the individual employee that has a ForeignKey of the base django user and a timesheet model that has a ForeignKey of the employee model. I'm not sure this is correct because when I use my registration form it just creates the base django user and not the "Employee" so when I want to create a new timesheet entry only the one employee is set up (set up with admin page). Can someone with more django experience tell me if there is a better way to do this (different model relationship, etc)
from django.urls import reverse
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
import datetime
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='employee')
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
# META CLASS
class Meta:
verbose_name = 'employee'
verbose_name_plural = 'employees'
# TO STRING METHOD
def __str__(self):
return f"{self.user}"
class Tsheet(models.Model):
# CHOICES
WORK_CHOICES= (
('W', 'Regular Work'),
('V', 'Vacation'),
('S', 'Sick',),
('C','Call In'),
)
# DATABASE FIELDS
name = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='name')
workType = models.CharField(max_length=15,choices=WORK_CHOICES)
workDate = models.DateField(verbose_name=("Date"), default=datetime.date.today, editable=True)
workDescription = models.CharField(max_length=200)
workHours = models.DecimalField(max_digits=4, decimal_places=2, default=8.00, verbose_name=("Hours"))
workReviewed= models.BooleanField(default=False)
slug = models.SlugField(max_length=50, unique=True,
help_text='Unique value for timesheet entry URL, created automatically from name.')
# META CLASS
class Meta:
verbose_name = 'tsheet'
verbose_name_plural = 'tsheets'
# TO STRING METHOD
def __str__(self):
return f"{self.name} - {self.workDate} - {self.workHours} - {self.workType}"
# SAVE METHOD
# ABSOLUTE URL METHOD
def get_absolute_url(self):
return reverse('entry-detail', kwargs={'pk': self.pk})```
The right way to approach this is to extend the AbstractUser and add the fields there:
class User(AbstractUser):
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
Then you have a single table with all the data from the default Django User as well as your specific fields