Django CharField as primary key still allow Null value to be save - mysql

Currently i have the following model that i would like to set CharField as primary key( my database is Mysql)
class Customer(models.Model):
class Meta:
db_table = "customers"
verbose_name = _('Customer')
verbose_name_plural = _('Customers')
customer_id = models.CharField(_('Customer ID'),max_length=255, primary_key=True)
name = models.CharField(_('Name'), max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
In the document it stated that :
primary_key=True implies null=False and unique=True. Only one primary
key is allowed on an object.
In Mysql the primary key has the following structure:
customer_id, type=varchar(255), collation=latin1_swedish_ci,
Attributes=blank Null=No, Default=None,Comments=blank, Extra=blank
but when i try to use the save() method with null value for primary key:
Customer.objects.create(customer_id=None, name="abc")
It still save Null value in the primary key without returning any error or validation, why is that?
EDIT:
After saving in Mysql it show the value of the customer_id=blank(when try to save it as None). Why it set to blank when saving customer_id=None?

When you create object for the first time
Customer.objects.create(customer_id=None, name="abc")
It will store customer_id value as '' (empty value, not null) and there are no other object we have created till now, so it's unique too.
Now when you again create an Customer object
Customer.objects.create(customer_id=None, name="xyz")
This will throw an error django.db.utils.IntegrityError: UNIQUE constraint failed: customers.customer_id because we already have empty value in our customer_id value. So, this is throwing an error of UNIQUE constraint

Do you use Django Rest Framework?
Then you may have add your customer_id to the read_only_fields in serializer.py
The result is:
You can't add an id in your request
Django doesn't recognised it as a required field anymore (except of Django Admin)
Django accepts a NULL value, which shouldn't be allowed

Related

Django ORM. How does Django know the auto incremented pk of newly created record

Suppose Book table has two fields id, name. id is auto incremented pk.
Database back end is mysql
In Django.
book = Book.objects.create(name="Tender Is the Night")
How does Django know new book's id.
What sql statements dose Django send to Mysql server?
Only the insert statement ? if so,does insert statement will return the id of newly created book?
It's a MySQL feature. See https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html
CREATE TABLE animals (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO animals (name) VALUES
('dog'),('cat'),('penguin'),
('lax'),('whale'),('ostrich');
SELECT * FROM animals;
No value was specified for the AUTO_INCREMENT column, so MySQL assigned sequence numbers automatically.
Django uses the relevant database API (MySQL, in this case) to find out the last inserted ID:
You can retrieve the most recent automatically generated AUTO_INCREMENT value with the LAST_INSERT_ID() SQL function or the mysql_insert_id() C API function. These functions are connection-specific, so their return values are not affected by another connection which is also performing inserts.
See the relevant source code for details: https://github.com/django/django/blob/master/django/db/models/base.py#L874
def _do_insert(self, manager, using, fields, update_pk, raw):
"""
Do an INSERT. If update_pk is defined then this method should return
the new pk for the model.
"""
return manager._insert([self], fields=fields, return_id=update_pk,
using=using, raw=raw)

Mysql UNIQUE Constraint that only applies if one field has specfic values

I hope this works somehow:
I have a Mysql Table with 3 column:
id = int
state = enum('visible', 'moderated', 'deleted')
user_id = int
No user should have more than 1 entry, that is 'visible' or 'moderated', but he can have endless 'deleted' entries.
So, I need a UNIQUE Key on user_id, that only applies if the state is 'visible' or 'moderated'
There are basically two options, first one is fairly simple but would require change in your table structure and application logic.
If you use NULL value instead of 'deleted', you can have as many "deleted" rows as you want for given user_id (having unique constaint on (user_id, state)). Your table structure would be something like this:
id = int
state = enum('visible', 'moderated') NULL
user_id = int
The other option would involve checking in post update/post insert triggers whether you are not breaking your contraint and throwing "sql exception" vide https://dev.mysql.com/doc/refman/5.5/en/signal.html

Rails ActiveRecord handle an id column that is not the primary key

Struggling with ActiveRecord auto assigning the :id attribute as the primary key even though it is a separate column.
Table - legacy-table
id - int
pk_id - int (primary key)
name - varchar2
info - varchar2
Model
class LegacyModel < ActiveRecord::Base
self.table_name = 'legacy-table'
self.primary_key = 'pk_id'
default_scope {order(:name => :asc)}
alias_attribute :other_id, :id
end
I don't care that ActiveRecord automatically assigns the primary key (pk_id) to the :id attribute however I lose all access to the actual id column. Trying to use the alias simply points me back at the primary key.
However one caveat to this issues is that from the view i can access the id column by using #legacymodel[:id]. But again when calling #legacymodel.id I get the value of the pk_id column. What i want is to be able to call #legacymodel.other_id and have it point to the id column. Instead #legacymodel.service_id, #legacymodel.id, and #legacymodel.pk_id all point to the same column pk_id
Please note that this is a legacy db and modifying the columns are out of the question. I am using Rails 4 with MySql.
Is there anyway to code around this? Why does #legacymodel[:id] give me different results then #legacymodel.id?
The answer by #cschroed did not work for me in the latest Rails (v4.2). Digging into the Rails source code, it appears that read_attribute will also use the primary key value if the key passed equals 'id':
ID = 'id'.freeze
# Returns the value of the attribute identified by <tt>attr_name</tt> after
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
name = attr_name.to_s
name = self.class.primary_key if name == ID
_read_attribute(name, &block)
end
https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/attribute_methods/read.rb
Since, the [] method uses read_attribute, this no longer works.
I found that directly reading from the attributes hash worked instead:
# LegacyModel class
def other_id
#attributes.fetch_value('id')
end
This provided a means of bypassing read_attribute by mimicking _read_attribute.
The read_attribute method will read a value out of the #attributes hash. The [] method uses read_attribute. So #legacymodel[:id] gets the value of the id column.
The write_attribute method always tries to translate id into the name of the primary key...
# ActiveRecord::AttributeMethods::Write
def write_attribute(attr_name, value)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
...and the []= method uses write_attribute. So #legacymodel[:id] = <value> will set a value into the primary key column, pk_id.
The id method is a special method that is aliased to the primary_key here:
# ActiveRecord::AttributeMethods::PrimaryKey
if attr_name == primary_key && attr_name != 'id'
generated_attribute_methods.send(:alias_method, :id, primary_key)
end
So #legacymodel.id will get the value of the pk_id column.
If you just want to read the id column through #legacymodel.other_id, then you could define a method like:
# LegacyModel class
def other_id
self[:id]
end
But if you also need to write to the id column through #legacymodel.other_id=, then you might need to try to find a safe way to override the write_attribute method so that you can work around the attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key statement.

Django Foreign Key

I am trying to generate a report across 2 models/ tables. Here they are:
class Members(models.Model):
username = models.CharField(max_length=30,null=True, unique=True)
email = models.CharField(max_length=100,null=True, unique=True)
name = models.CharField(max_length=30,null=True)
phone = models.CharField(max_length=30,null=True)
and
class Report(models.Model):
report_text = models.CharField(max_length=500)
reporter_id = models.IntegerField(db_index=True)
reported_id = models.IntegerField(db_index=True)
date_created = models.DateTimeField(null=True)
date_read = models.DateTimeField(null=True)
The 2 tables obviously have auto increment IDs as the primary key.
The report will look like this:
Reported Phone | Reported Name | Report | Date Reported | Date Report Read
Everyone reported on will be in the member table. The reporter ID is the ID of the member who logged the report. The reported_id is the ID of the person the report is on. I need to do a join across the 2 models to get the members name and their phone number. I can't quite work it out form the doc. I believe I should make the reported_id and reporter_id both foreign keys to the Members table primary key ID field. How do I do that and what code will extract the report for all entries submitted by a specific reporter?
Do I user reported_id = models.ForeignKey(Members) and do the same for reporter_id. It seems odd as I don't specify the field that the field is foreign to. The ORM is supposed to make it easier (and it usually does!). I could do it with a join in SQL but this has got me stumped.
I hope the question makes sense.
Thanks in advance
Rich
How do I do that and what code will
extract the report for all entries
submitted by a specific reporter?
Yes, do reported_id = models.ForeignKey(Members)
The field will be the target models primary key, which is in your case id since you haven't specified one.
You will need to specify a related_name for one of these fields to prevent a name clash for the reverse foreign key accessor.
After setting up the foreign key field, to get all objects related via that foreign key, use the related_name to query the related model.
http://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
For example, if you set up your model as:
reporter = models.ForeignKey(Members, related_name="reports_by_me")
reported = models.ForeignKey(Members, related_name="reports_by_others")
You could access all related Report models via that foreign key by
member_instance.reports_by_me.all()
member_instance.reports_by_others.all()

Unique constrain with condition MYSQL

I have User table in my DB.
A user has the fields name, company_id and status: boolean, 1- live, 0- deleted.
When a user is deleted, his status is set to 0.
The combination of a live user name in a company should be unique. After a user is deleted, I don't mind that a user should be created with the same name for the company.
My question is how do I define a uniuqe constrain for the fields name, company_id and status=1 (It's not a uniuqe constrain on those three field becuase I don't mind that the combination of name-company_id-0 will appear a few times in the table).
Thanks,
Dvora
Use NULL value for deleted users.
Unique key allows unlimited number of NULL values.
Update: Don't touch user name, NULL in status field is enough.
Which programming language you are using?
your logic shoule be as follows
select * from Table_name where name='' AND company_id = '' AND status = 1
if this return any rows give uniqueness error to the user else create it.
I would create another column to store a deleted user's previous name and set their real name to NULL when they're deleted (as well as setting the status to 0).
Then have a unique constraint on name and company. NULLs will not affect the uniqueness (since NULL != NULL) and you can still recover the user's original name if desired.
So the delete operation is something like:
update users
set prev_name = name,
name = null,
status = 0
where name = 'paxdiablo' and company = 'SmallGreen';
Would it be easier if you split "live" and "deleted" so they have their own tinyint/boolean columns ?
I would replace status field with deleted_at (datetime). When the user is active its value would be NULL, but when deleted, it would be set to current datetime.
Next, i would add unique index on username & deleted_at fields, which will allow me to delete more than one user with the same username (in at least 1 second interval).