I am adding a new field to an existing db table. it is to be auto-generated with strings.
Here is my code:
from django.utils.crypto import get_random_string
...
Model:
verification_token = models.CharField(max_length=60, null=False, blank=False, default=get_random_string)
I generate my migration file with ./manage.py makemigrations and a file is generated.
I verify the new file has default set to field=models.CharField(default=django.utils.crypto.get_random_string, max_length=60)
so all seems fine.
Proceed with ./manage.py migrate it goes with no error from terminal.
However when i check my table i see all the token fields are filled with identical values.
Is this something i am doing wrong?
How can i fix this within migrations?
When a new column is added to a table, and the column is NOT NULL, each entry in the column must be filled with a valid value during the creation of the column. Django does this by adding a DEFAULT clause to the column definition. Since this is a single default value for the whole column, your function will only be called once.
You can populate the column with unique values using a data migration. The procedure for a slightly different use-case is described in the documentation, but the basics of the data migrations are as follows:
from django.db import migrations, models
from django.utils.crypto import get_random_string
def generate_verification_token(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
for row in MyModel.objects.all():
row.verification_token = get_random_string()
row.save()
class Migration(migrations.Migration):
dependencies = [
('myapp', '0004_add_verification_token_field'),
]
operations = [
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(generate_verification_token, reverse_code=migrations.RunPython.noop),
]
Just add this in a new migration file, change the apps.get_model() call and change the dependencies to point to the previous migration in the app.
It maybe the token string to sort, so django will save some duplicates values. But, i'm not sure it is your main problem.
Anyway, I suggest you to handle duplicates values using while, then filter your model by generated token, makesure that token isn't used yet. I'll give you exampe such as below..
from django.utils.crypto import get_random_string
def generate_token():
token = get_random_string()
number = 2
while YourModel.objects.filter(verification_token=token).exists():
token = '%s-%d' % (token, number)
number += 1
return token
in your field of verification_token;
verification_token = models.CharField(max_length=60, unique=True, default=generate_token)
I also suggest you to using unique=True to handle duplicated values.
Related
So my question was how I can generate a random UID or slug for my CMS. If I use the default id which is coming from API v2 people can easily guess my next post URL easily.
Is there any way to add a unique slug/ID/UUID for my wagtail CMS?
Here is the simple solution, go to your bolg/models.py and first install pip install django-autoslug
Then import this
from django.db.models import CharField, Model
from autoslug import AutoSlugField
from django.utils.crypto import get_random_string
Here we are adding another extension called get_random_string which will generate a random string every time you call it.
Then add this in your AddStory {Your add post class}
#Defining a function to get random id every call
def randomid(self):
return(get_random_string(length=10))
# creating a custom slug to show in frontend.
news_slug = AutoSlugField(populate_from='randomid', unique = True, null= True, default=None)
Here I defined a function called randomid which will return a 10 digit string on every call. Then I created a new field called news_slug which is coming from Django auto_slug extension, wich will populate from the randomid, and the URL must unique (ex: if it all 10 digit string are finished it will add -1,-2 so on ( ex: sxcfsf12e4-1), here null = true means that this field can be empty so that autoslug can generate unique ID.
Then expose that news_slug filed in API.
api_fields=[
APIField("news_slug"),
]
you can access all field like this /api/v2/pages/?type=blog.AddStory&fields=*
Here type=blog is your blog app and AddStory is your class.
Hope this helps, it took time for me to find out. More wagtail tutorials will come.
A variant on the answer that I use for user ID's:
import random
import string
from django_extensions.db.fields import AutoSlugField
....
class CustomUser(AbstractUser):
....
uuid = AutoSlugField(unique=True)
....
def slugify_function(self, content):
return ''.join((random.choice(string.ascii_letters + string.digits) for i in range(12)))
AutoSlugField is part of django_extensions
AutoSlugField has a built in slugify_function to generate the slug, you can override that just by declaring your own in the class
This slugify_function will generate a random 12 alpha-numeric character string including upper/lower case. Permutations are (I think) 1 e21 so chances of guessing are extremely slim.
I am trying to change the field type of one of attributes from CharField to DecimalField by doing an empty migrations and populate the new field by filling the migrations log with the following:
from __future__ import unicode_literals
from django.db import migrations
from decimal import Decimal
def populate_new_col(apps, schema_editor): #Plug data from 'LastPrice' into 'LastPrice_v1' in the same model class 'all_ks'.
all_ks = apps.get_model('blog', 'all_ks')
for ks in all_ks.objects.all():
if float(ks.LastPrice): #Check if conversion to float type is possible...
print ks.LastPrice
ks.LastPrice_v1, created = all_ks.objects.get_or_create(LastPrice_v1=Decimal(float(ks.LastPrice)*1.0))
else: #...else insert None.
ks.LastPrice_v1, created = all_ks.objects.get_or_create(LastPrice_v1=None)
ks.save()
class Migration(migrations.Migration):
dependencies = [
('blog', '0027_auto_20190301_1600'),
]
operations = [
migrations.RunPython(populate_new_col),
]
But I kept getting an error when I tried to migrate:
TypeError: Tried to update field blog.All_ks.LastPrice_v1 with a model instance, <All_ks: All_ks object>. Use a value compatible with DecimalField.
Is there something I missed converting string to Decimal?
FYI, ‘LastPrice’ is the old attribute with CharField, and ‘LastPrice_v1’ is the new attribute with DecimalField.
all_ks.objects.get_or_create() returns an All_ks object which you assign to the DecimalField LastPrice_v1. So obviously Django complains. Why don't you assign the same ks's LastPrice?
ks.LastPrice_v1 = float(ks.LastPrice)
That said, fiddling around with manual migrations seems a lot of trouble for what you want to achieve (unless you're very familiar with migration code). If you're not, you're usually better off
creating the new field in code
migrating
populating the new field
renaming the old field
renaming the new field to the original name
removing the old field
migrating again
All steps are vanilla Django operations, with the bonus that you can revert until the very last step (nice to have when things can take unexpected turns as you've just experienced).
I worte a little qna script for my website and to prevent users from starting a discussion I want every user only to be able to reply once.
class Q(models.Model):
text = models.TextField()
user = models.ForeignKeyField('auth.User')
class A(models.Model):
text = models.TextField()
user = models.ForeignKeyField('auth.User')
q = models.ForeignKeyField('Q')
class Meta:
unique_together = (('user','q'),)
Now the the migration gives me:
return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: columns user_id, q_id are not unique
Of course the unique clashes with existing data. What I need to know now is how to tell the migration to delete the conflicting answers. A stupid solution like keeping the first one found would already be a big help. Even better would be a way to compare conflicting A by a custom function.
I'm running Django-1.7 with the new migration system - not South.
Thanks you for your help!
You just need to create a data migration, where you can indeed write a custom function to use any logic you want. See the documentation for the details.
As an example, a data migration that kept only the lowest Answer id (which should be a good proxy for the earliest answer), might look like this:
from django.db import models, migrations
def make_unique(apps, schema_editor):
A = apps.get_model("yourappname", "A")
# get all unique pairs of (user, question)
all_user_qs = A.objects.values_list("user_id", "q_id").distinct()
# for each pair, delete all but the first
for user_id, q_id in all_user_qs:
late_answers = A.objects.filter(user_id=user_id, q_id=q_id).order_by('id')[1:]
A.objects.filter(id__in=list(late_answers)).delete()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(make_unique),
]
(This is off the top of my head, and is of course destructive, so please just take this as an example. You might want to look into backing up your data before doing all this deleting.)
To recap: Delete the migration you're trying to run and get rid of the unique constraint. Create an empty data migration as described in the documentation. Write a function to delete the non-unique data that currently exists in the database. Then add the unique constraint back in and run makemigrations. Finally, run both migrations with migrate.
I'm reading this tutorial to lear the basics of django and I'm facing a problem I can't solve.
I'm at this page http://www.tangowithdjango.com/book17/chapters/models_templates.html at the sluglify function approach.
In the tutorial the author says we have to create a new line in our category model for th slugfield. I folow strictly all the steps just as I show here:
from django.db import models
from django.template.defaultfilters import slugify
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
likes = models.IntegerField(default=0)
views = models.IntegerField(default=0)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.name
class Page(models.Model):
category = models.ForeignKey(Category)
title = models.CharField(max_length=128)
url = models.URLField()
views = models.IntegerField(default=0)
def __unicode__(self):
return self.title
When I run the "makemgiration" command everything works as expected: I choose the first option and provide ‘’ . BUT when I run "migrate" I get:
django.db.utils.Integrity error: Slug column is not unique
What is going on here? I've repeated several times the migrations and tried other default codes but with the same ending. I can't figure out what im doing wrong. They only thing left is that i'm giving something else instead of ‘’ (Firstly I thoughtthey were '' or ").
Thankyou for your time and help!
Delete db.sqlite3 and re run
python manage.py syncdb
Then perform the migrations
python manage.py makemigrations rango
python manage.py migrate
and re run your population script. This is a downside of Django that whenever you change models.py the db must be recreated.
I am also going through the tutorial and I was having the same issue a couple days ago.
I believe the problem is that you are trying to add a new field (slug) and have it be unique for each of the elements in your table but if I'm not mistaken you already have some data in your table for which that value does not exist and therefore the value that this field would get for those rows is not unique, hence the "django.db.utils.Integrity error: Slug column is not unique".
What I did was simply to delete the data in the table (because it was only a couple of fields, so no big deal) and THEN perform the addition of the field to the model. After doing that, you can put the data back in the table (if you're following the tutorial you should have a script for automated table population so you just run that script and you're golden). Once you have done that, all the rows in the table should have a unique slug field (since it is automatically generated based on the category name) and that solves the problem.
Note that if your table is very large, this may not be a very good solution because of the deletion of data so perhaps there is a better way, like adding that field to the model without it being unique, then running a script that sets the slug field for every row to an unique value and then setting the model's field as unique but I'm not very knowledgeable on SQL and the first solution worked just fine for me so it should work for you as well.
try deleting the sqlite3.db file from the project's directory. i was stuck on a similar problem and that really works..also even if you modify your population script, you have to delete and the recreate the db file to see the changes made....
I have a model with a CharField field with a default value of uuid4:
f = models.CharField(default=uuid4, max_length=36, unique=True, blank=True)
and this is causing the following error:
Cannot successfully create field 'f' for model
'm': name 'UUID' is not defined.
running the migrate commmand! Ho can I fix this issue? so far I tried:
to define a "wrapper function" in the module for uuid (ie: def getUUID())
to set the default value of "f" by overriding the Model constructor
...but the problem remains :(
ps. I know that I can instruct south for custom fields, but I'm not using custom fields in my opinion :P
I solved defining the following helper function in my model's module:
from uuid import uuid4
def generateUUID():
return str(uuid4())
then:
f = models.CharField(default=generateUUID, max_length=36, unique=True, editable=False)
south will generate a migration file (migrations.0001_initial) with a generated UUID like:
default='5c88ff72-def3-4842-8d48-a75bb3240bb5'
this is pretty unhappy... since that string is "static", instead it must be created dynamically using the helper function... anyway in the django's world al seems working as expected... I added some records into the database and a new UUID was generated for each one. I then tried my first schema migration by adding a couple of fields to my model and they has been added to the database table as expected.
You can also import UUID in your migration:
from uuid import UUID
I simply removed a uuid directory from 'node_modules' directory.
And then I reinstall uuid and it worked.
I hope it helped you guys <3
step 1 : install uuid
npm install uuid
step 2: then import it
import { v4 as uuid } from 'uuid';
step 3: Use it
id:uuid()