I have the following model:
class User(models.Model):
email = models.EmailField(max_length=254, null=False, unique=True)
referral_code = models.CharField(max_length=10, null=False, unique=True)
And used the Django shell to save a user instance with referral_code undefined:
u = User(email="test#example.com")
u.save()
This did not raise an exception. My understanding was that null=False would require referral_code to be set - Is this not the case? How would I achieve that behaviour?
update
I notice that the field is set to u.referral_code='', so given the uniqueness constraint, this has raised an exception when I tried to repeat the process with a new instance. I would rather the exception was thrown because I did not set the field...
The value of your referral_code is not null, it is '' (blank string). This is the default of CharField.
Updated along with question:
You can raise an error before the data is stored in the database by overriding save on the model
class User(models.Model):
email = models.EmailField(max_length=254, null=False, unique=True)
referral_code = models.CharField(max_length=10, null=False, unique=True)
def save(self, *args, **kwargs):
assert self.email, "The 'email' field must be populated."
super().save(*args, **kwargs)
It should be noted that this is not to be preferred over form validation where possible.
Update now that newer features of Django are available:
Django's Constraints allow you to add custom database constraints:
class User(models.Model):
email = models.EmailField(max_length=254, null=False, unique=True)
referral_code = models.CharField(max_length=10, null=False, unique=True)
class Meta:
constraints = [
models.CheckConstraint(
check=(~models.Q(referral_code='')),
name='referral_code_populated',
)
]
Related
How do I dynamically alter field labels in forms in Django
In the below code, the labels word in the def clean_passport function is 'greyed out', saying 'labels' not accessed
Also, 'document_type' is not submitted in the form, it is not included as a form field, but is in the model, and it is assigned a value in the view.... how do I access it, if I need to do customised form validation based on the value of document_type???
Or should I put the logic in the model, and change the field characteristics there? Again, how would I access the value of document_type of a record / object in the model? Can you do that, dynamically change field attributes on a database?
UPDATE
I think I may need to create 4 separate forms as a way around this, unless anyone has a more generalisable / flexible solution
FORM
class LegalDocumentUpdateForm(forms.ModelForm):
# FORM META PARAMETERS
class Meta:
model = Legal_Document
fields = ('name_on_document', 'number_on_document', 'issuing_country', 'issuance_date', 'expiry_date', 'document_location')
labels = {
'name_on_document': _("Name on Document*"),
'number_on_document': _("Document Number"),
'issuing_country': _("Issuing Country"),
'issuance_date': _("Issuance Date"),
'expiry_date': _("Expiry Date"),
'document_location': _("Document Location*")
}
# SANITIZATION & VALIDATION CHECKS
def clean(self):
document_type = self.cleaned_data['document_type']
# document_type is not submitted in the form, but is in the model, and preset in the view.... how do I access it, if I need to do form validation depending on the value of document_type???
if document_type == 'Passport':
number_on_document = self.cleaned_data['number_on_document']
issuing_country = self.cleaned_data['issuing_country']
expiry_date = self.cleaned_data['expiry_date']
if number_on_document == None:
raise forms.ValidationError(_("Please enter the Passport Number."))
if issuing_country == None:
raise forms.ValidationError(_("Please enter the Passport's Issuing Country."))
if expiry_date == None:
raise forms.ValidationError(_("Please enter the Passport's Expiry Date."))
labels = {
'name_on_document': _("Passport Full Name*"),
'number_on_document': _("Passport Number*"),
'issuing_country': _("Issuing Country*"),
'expiry_date': _("Passport Expiry Date*"),
}
MODEL
# LEGAL DOCUMENT MODEL
class Legal_Document(models.Model):
class LegalDocumentTypes(models.TextChoices):
PASSPORT = 'Passport', _('Passport')
BIRTHCERT = 'Birth Certificate', _('Birth or Adoption Certificate')
CERTOFREG = 'Certificate of Registration', _('Certificate of Registration or Naturalisation')
NIPROOF = 'Proof of N.I.', _('Proof of N.I. Document')
related_user = models.OneToOneField(User, on_delete=models.CASCADE, null=False, default='')
document_id = models.BigAutoField(verbose_name='Document ID', primary_key=True, serialize=False, auto_created=True)
document_type = models.CharField(verbose_name='Document Type', max_length=27, choices=LegalDocumentTypes.choices, blank=False, null=False, default=LegalDocumentTypes.PASSPORT)
name_on_document = models.CharField(verbose_name='Name on Document', max_length=100, blank=False, null=False, default='')
number_on_document = models.CharField(verbose_name='Document Number', max_length=20, blank=True, null=False, default='')
issuing_country = CountryField(verbose_name='Issuing Country', max_length=100, blank=True, null=False, default='')
issuance_date = models.DateField(verbose_name='Issuance Date', blank=True, null=True)
expiry_date = models.DateField(verbose_name='Expiry Date', blank=True, null=True)
document_location = models.CharField(verbose_name='Document Location', max_length=30, blank=False, null=False, default='')
Each field has a label attribute which you can set.
E.g.
self.fields['name_on_document'].label = 'Whatever'
Maybe you can use that in the clean method. But I don't see the point as it won't be displayed unless there is an error in the form.
I want to change one of the existing field names in the Django model. But, for the backward-compatibleness, we'd like not to override the existing field with the new one, keep both of them for now. Is there any way to have multiple fields referring to the same database object? i.e
Code right now:
class NetworkPackage:
name = models.CharField(unique=True, blank=False, null=False)
inbound = models.CharField(unique=True, blank=False, null=False)
...
I want to implement:
class NetworkPackage:
name = models.CharField(max_length=32, unique=True, blank=False, null=False)
inbound = models.CharField(max_length=128, unique=True, blank=True)
mobile = models.CharField(max_length=128, unique=True, blank=True)
...
Basically, 'inbound' and 'mobile' should refer to the same field and the request could be sent either with 'inbound' field or 'mobile'.
It's a bad idea having two fields within the same model that hold the same info, especially if you need to enforce uniqueness because
You'll need to maintain parity for both fields, so that means if the request was setting inbound, then you'll also have to set mobile.
The database now has to index both inbound and mobile due to uniqueness.
What you can do is utilize python properties as properties are perfect solutions for cases where you have legacy attributes:
class NetworkPackage(models.Model):
name = models.CharField(unique=True, blank=False, null=False)
inbound = models.CharField(unique=True, blank=False, null=False)
...
#property
def mobile(self):
return self.inbound
#mobile.setter
def mobile(self, value):
self.inbound = value
Then in your serializer, you need to:
Add mobile as an additional field sourcing to inbound.
Override the required and allow_blank arguments on both fields since the serializer can allow either fields...
BUT, you'll then need to write a custom validation method to ensure at least 1 of the 2 fields are populated with a value.
Also prioritize the inbound value over the mobile value if both fields are populated.
class NetworkPackageSerializer(serializers.ModelSerializer):
inbound = serializers.CharField(required=False, allow_blank=True)
mobile = serializers.CharField(source="inbound", required=False, allow_blank=True)
class Meta:
model = NetworkPackage
fields = ("inbound", "mobile", ...)
def validate(self, data):
"""Validate `inbound` and/or `mobile`."""
if not data["inbound"] and not data["mobile"]:
raise serializers.ValidationError("missing value on inbound or mobile")
if data["inbound"]:
del data["mobile"]
else:
del data["inbound"]
return data
Not sure why do you make duplicate fields, but I have some suggestions for you.
1. Custom property
class NetworkPackage:
name = models.CharField(unique=True, blank=False, null=False)
inbound = models.CharField(unique=True, blank=False, null=False)
#poperty
def mobile(self):
return self.inbound
2. Serializer
class NetworkPackageSerializer(serializers.Serializer):
mobile = serializers.CharField(source='inbound')
class Meta:
model = NetworkPackage
fields = (
'id',
'inbound',
'mobile',
'name',
...
)
I have two models shown as follows. I want to be able to execute this query through the django ORM, essentially giving me the CustomUser class alongside two derived fields: max(message.sent_at) and max(case when read_at is null then 1 else 0 end). Those two fields would enable me to sort threads of messages by usernames and latest activity.
Here are my classes:
class CustomUser(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
username = models.CharField(_('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'), validators=[username_validator], error_messages={'unique': _('A user with that username already exists.'),},)
email = models.EmailField(_('email address'), blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'),)
is_active = models.BooleanField(_('active'), default=True, help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'),)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
bio = models.TextField(max_length=500, null=True, blank=True)
location = models.CharField(max_length=30, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
phone_number = PhoneNumberField(default='+10000000000')
gender = models.CharField(max_length=32, choices=[(tag.name, tag.value) for tag in GenderChoice], default=GenderChoice.UNSPECIFIED.value)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['username']
verbose_name = _('user')
verbose_name_plural = _('users')
and
class Message(AbstractIP):
subject = models.CharField(_('Subject'), max_length=120, blank=True)
body = models.TextField(_('Body')) # Do we want to cap length or enforce non-blank?
sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender_messages', verbose_name=_('Sender'), on_delete=models.CASCADE)
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='receiver_messages', verbose_name=_('Recipient'), blank=True, on_delete=models.CASCADE)
parent_msg = models.ForeignKey('self', related_name='next_messages', null=True, blank=True, verbose_name=_('Parent message'), on_delete=models.CASCADE)
sent_at = models.DateTimeField(_('sent at'), null=True, blank=True)
read_at = models.DateTimeField(_('read at'), null=True, blank=True)
replied_at = models.DateTimeField(_('replied at'), null=True, blank=True)
sender_deleted_at = models.DateTimeField(_('Sender deleted at'), null=True, blank=True)
recipient_deleted_at = models.DateTimeField(_('Recipient deleted at'), null=True, blank=True)
ip = models.GenericIPAddressField(verbose_name=_('IP'), null=True, blank=True)
user_agent = models.CharField(verbose_name=_('User Agent'), blank=True, max_length=255)
objects = MessageManager() # Manager for Message queries
def new(self):
"""Returns whether the recipient has read the message or not"""
if self.read_at is not None:
return False
return True
def replied(self):
"""Returns whether the recipient has written a reply to this message"""
if self.replied_at is not None:
return True
return False
def __str__(self):
if self.subject is not None:
return self.subject
if self.body is not None:
return self.body[:40]
return None
def get_absolute_url(self):
return reverse('messages_detail', args=[self.id])
def save(self, **kwargs):
if not self.id:
self.sent_at = timezone.now()
super(Message, self).save(**kwargs)
class Meta:
ordering = ['-sent_at']
verbose_name = _('Message')
verbose_name_plural = _('Messages')
The query I want to be able to perform equates to this, but I cannot figure out how to do it in the ORM, where %s is a placeholder for the CustomUser.id (pk) field of a given user.
SELECT webrtc_customuser.*
,MAX(webrtc_message.sent_at) AS sent_at
,MAX(CASE WHEN webrtc_message.read_at IS NULL AND webrtc_customuser.id <> webrtc_message.sender_id THEN 1 ELSE 0 END) AS has_unread
FROM webrtc_customuser
INNER JOIN webrtc_message
ON (
webrtc_customuser.id = webrtc_message.sender_id
AND webrtc_message.sender_id = %s
AND webrtc_message.sender_deleted_at IS NULL
) OR (
webrtc_customuser.id = webrtc_message.recipient_id
AND webrtc_message.recipient_id = %s
AND webrtc_message.recipient_deleted_at IS NULL
)
I managed to get the correct user_id and derived fields with the following queries but cannot figure out how to get the CustomUser properties joined alongside them.
messages = self.values(
user_fk=Case(When(sender=user, then='recipient'), default='sender', output_field=models.IntegerField())
).exclude(
sender=user, recipient=user
).filter(
Q(sender=user, sender_deleted_at__isnull=True) |
Q(recipient=user, recipient_deleted_at__isnull=True)
).annotate(
max_sent_at=Max('sent_at'),
has_unread=Max(Case(When(~Q(sender=user) & Q(read_at__isnull=True), then=1), default=0, output_field=models.IntegerField()))
).order_by()
Thank you in advance for your time!
Edit: updated ORM query
You need to specify the desired user properties individually:
messages = self.values(
user_email=Case(When(sender=user, then='recipient__email'), default='sender__email'),
user_username=Case(When(sender=user, then='recipient__username'), default='sender__username'),
)
Not very pretty, particularly as you have to repeat the CASE statement for every column and may even need to specify an output_field for every one.
To get around that, ie. to get all user properties without selecting them one by one, you'd either need to a) select from CustomUser.object (figuring out how to select the relevant users and get the relevant annotations), or b) select full message objects rather than just a values() dictionary. Then you can access the full user objects via message.senderand message.recipient. But here again, the challenge would be how to filter and annotate the messages queryset using subqueries, since just omitting values() will bust the aggregates in your annotations as every message object will then be unique.
I am trying to create tables using python/django and postgres. I create classes for each database table in models.py. When I try to create the migrations it gives me this errors:
File "..\database\models.py", line 40, in <module>
class group(models.Model):
File "database\models.py", line 41, in group
user_id = models.ForeignKey(user) TypeError: __init__() missing 1 required positional argument: 'on_delete'
This is my code
class user(models.Model):
STATUS_CHOICES = (
('active','Active'),
('not active', 'Not active')
)
user_id= models.AutoField(primary_key=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(max_length=70, null=True, blank=True, unique=True)
phone = models.CharField( max_length=17, blank=True)
gender = models.CharField(max_length=50)
region = models.CharField(max_length=50)
password = models.CharField(widget=models.PasswordInput)
active = models.CharField(max_length=50, choices=STATUS_CHOICES, default='not active')
register_date = models.DateField(default=date.today)
def _str_(self):
return self.user_id
class train(models.Model):
train_CHOICES = (
('intercity', 'Intercity'),
('sprinter', 'Sprinter')
)
train_id= models.AutoField(primary_key=True)
train_type = models.CharField(max_length=50, choices=train_CHOICES, default='intercity')
def _str_(self):
return self.train_id
class group(models.Model):
user_id = models.ForeignKey(user)
group_id = models.AutoField(primary_key=True)
trip_id = models.ForeignKey(trip)
created_date = models.DateField(default=date.today)
started_at = models.DateTimeField()
def _str_(self):
return self.group_id
I am really new to Python. I assume there are many mistakes. Could you please give a hint?
Since Django 2.0, on_delete is required argument for ForeignKey (in older versions it defaults to CASCADE), you should add it for all ForeignKey fields:
user_id = models.ForeignKey(user, on_delete=models.CASCADE)
trip_id = models.ForeignKey(trip, on_delete=models.CASCADE)
Note on_delete=models.CASCADE means that on deleting user or trip object Django deletes the objects containing the ForeignKey. You can find list of other options here.
i am getting error django model like this when i try to makemigrations:
You are trying to add a non-nullable field 'person' to owner without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
i use django 1.8 and this is my model:
class Person(models.Model):
user = models.OneToOneField(User)
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', message='hanya yang mengandung karakter alphanumeric')
email = models.EmailField(verbose_name='email address', unique=True, max_length=244)
username = models.CharField(unique=True, max_length=20, validators=[alphanumeric])
first_name = models.CharField(max_length=30, null=True, blank=True)
last_name = models.CharField(max_length=30, null=True, blank=True)
date_of_birth = models.DateTimeField()
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def get_full_name(self):
fullname = self.first_name+" "+self.last_name
return self.fullname
def get_short_name(self):
return self.username
def list_operator(self):
return self.operators.all()
def __str__(self):
return self.email
class Operator(models.Model):
person = models.ForeignKey(Person, related_name="operators", null=True)
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', message='hanya yang mengandung karakter alphanumeric')
email = models.EmailField(verbose_name='email address', unique=True, max_length=244)
username = models.CharField(unique=True, max_length=20, validators=[alphanumeric])
first_name = models.CharField(max_length=30, null=True, blank=True)
last_name = models.CharField(max_length=30, null=True, blank=True)
date_of_birth = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.username;
i want to know where my code is wrong.
can you help me solved this problem?
Your code isn't wrong. Just follow the instructions provided by the message...
The person field within your Operator model can't be null (because null=True isn't set). You must already have Operators in your database, so Django doesn't know what to do with those.
You need to either: (a) provide a default value in your model, (b) provide a default during the migration process, or (c) enable null values for that field.