Migrating data with south to a new model not working - django

I have an abstract model that I'm converting to a concrete model. I'm successfully using south to change the schema, but I'm unable to use the datamigration.
My initial state is:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, \
related_name='profile')
class Meta:
abstract=True
class SpecificProfile(UserProfile):
url = models.URLField()
My new state is:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, \
related_name='profile')
class SpecificProfile(UserProfile):
user_profile = models.OneToOneField(UserProfile, parent_link=True)
url = models.URLField()
My schema migration is:
class Migration(SchemaMigration):
def forwards(self, orm):
# Renaming field 'SpecProfile.user_profile'
db.rename_column('specificprofile', 'user_id', 'user_profile_id')
# Adding model 'UserProfile'
db.create_table('userprofile', (
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='profile', unique=True, primary_key=True, to=orm['auth.User'])),
))
db.send_create_signal('myapp', ['UserProfile'])
I edited the file generated by south in order to rename one field in SpecificProfile
Now, in the data migration process, I would like to create one UserProfile entry per SpecificProfile and assign UserProfile.user_id to SpecificProfile.user_profile_id.
So, my data migration forwards is:
class Migration(DataMigration):
def forwards(self, orm):
for spec in orm.SpecificProfile.objects.all():
user_profile = orm.UserProfile()
user_profile.user_id = spec.user_profile_id
user_profile.save()
The script runs without errors but does not create any new entry in UserProfile table.
Should I use UserProfile() instead of orm.UserProfile()?
Any ideas?

SpecificProfile.user_profile_id didn't exist previously, so it has no data to migrate. What you really are wanting to do is set user_profile.user to spec.user, and then set spec.user_profile to user_profile.
def forwards(self, orm):
for spec in orm.SpecificProfile.objects.all():
user_profile = orm.UserProfile()
user_profile.user_id = spec.user_id
user_profile.save()
# Then,
spec.user_profile_id = user_profile.id
However, once you've done the initial migration, I'm pretty sure SpecificProfile.user doesn't exist anymore. South removes that field since it's now on UserProfile.

Related

Need to get the Foreign key value instead of ID in the database table using Django

model
class Project(models.Model):
project_name = models.CharField(max_length=20)
client= models.ForeignKey(Client,on_delete=CASCADE,related_name="Client1",default=None)
user=models.ManyToManyField(Default_User,related_name='users',default=None)
description=models.TextField()
type=models.TextField() #dropdown
start_date = models.DateTimeField(max_length=10)
end_date=models.DateTimeField(max_length=10)
technical_contact_name = models.CharField(max_length=30)
email=models.EmailField(max_length=254,default=None)
phone = PhoneField(blank=True)
delivery_head_contact_name=models.CharField(max_length=30)
class Meta:
db_table ='Project'
def __str__(self):
return self.project_name
model
class Job(models.Model):
job_name=models.CharField(max_length=50)
user= models.ForeignKey(Default_User,on_delete=CASCADE)
project = ChainedForeignKey(Project,chained_field="user", chained_model_field="user",related_name='projects',show_all=False, auto_choose=True, sort=True)
date = models.DateField(max_length=10,default=None)
class Meta:
db_table ='Job'
def __str__(self):
return '{}'.format(self.job_name)
serializers
class ProjectSerializers(serializers.ModelSerializer):
class Meta:
model= Project
fields= '__all__'
class Job_Serializers(serializers.ModelSerializer):
job = serializers.StringRelatedField()
class Meta:
model= Job
fields= ('id','job_name','user','project','date','job',)
I need to get the foreign key value displayed in the database table of Job model but as per the above code it is displaying the Foreign key ID. For example I linked the project model in the Job model and in the db table it is showing the Project_id as(1,2,3) but i need to return the values of that id as(app, learning etc). Please help me to get the values of the foreign key value instead of ID in the database table.
The database will by default take the unique field from the model and django provide id as unique key for models. It is for data consistency. So you can let that happen and in job serializera use SerializerMethodField to retrieve the value of project name based on instance of job objects.
Depends on what you want to achieve with it. If it is just to return another field value from project, then you can add it to the serializer as in below example. I am returning project_name as well.
class JobSerializers(serializers.ModelSerializer):
job = serializers.StringRelatedField()
project_name = serializers.SerializerMethodField()
class Meta:
model= Job
fields= ('id','job_name','user','project','date','job', 'project_name')
def get_project_name(self, job):
return job.project.project_name
If you want to return the whole project object then you have to include
project = ProjectSerializers()

Django UniqueConstraint doesn't works with ManyToManyField, got exception FieldDoesNotExist

The migration stopped working when switching from ForeignKey between Shop and user to a ManyToManyField. I wanted shops to be able to be owned by different users at the same time:
class Shop(models.Model):
name = models.CharField('name', max_length=120)
#user = models.ForeignKey(User, on_delete=models.CASCADE) ##before
shopuser= models.ManyToManyField(User, related_name="shopusers", blank=True) ##after
class Meta:
constraints = [models.UniqueConstraint(fields=['shopuser', 'name'], name='user cant have the same shop twice!')]
## after:
#property
def get_shopuser(self):
return ", ".join([u.username for u in self.shopuser.all()])
class Warehouse(models.Model):
address = models.CharField('address', max_length=120)
user = models.ManyToManyField(User, related_name="User", blank=True)
django.core.exceptions.FieldDoesNotExist: NewShop has no field named 'shopusers'
I thought by choosing a related name I can use multiple relations to the User model? I already tried completely deleting my database (and migrations folder) and migrate from start, but tht did not help :(
Example where I would call the code:
admin.py:
#admin.register(Shop)
class ShopAdmin(admin.ModelAdmin):
list_display = ("name", "related_shopuser")
list_filter = ("name", "shopuser")
fieldsets = [("Requrired Information", {"description": "These fields are required",
"fields": (("name", "shopuser"))}),]
def related_shopuser(self, obj):
return obj.get_shopuser
Where does Djanog migrations get the NewShop from FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))? Is it automatically generated from the ModelName Shop?
Issue
The UniqueConstraint on ManyToManyField will not work as expected. If you want to enforce the constraint, you should define a intermediate model and connect them using the through--(Django doc) parameter.
For the sake of simplicity, I assume you have a model as below,
class Shop(models.Model):
name = models.CharField('name', max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'user',
'name'
],
name='user cant have the same shop twice!'
)
]
and now you want make the user field to a ManyToManyField from ForeignKey
Note: I have changed the field name to users from user, which is more approriate.
Method-1 : Remove unique constraint
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User)
Now, run makemigrations and migrate commands
Method-2: use an intermediate model
class ShopUserIntermediateModel(models.Model):
shop = models.ForeignKey('Shop', models.CASCADE)
user = models.ForeignKey(User, models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'shop',
'user'
],
name='user cant have the same shop twice!'
)
]
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User, through=ShopUserIntermediateModel)
If not required (the project is already in production), try deleting your migration files, or at least the migration where the user field was added in the first time

Create multiple model instances of interlinked models through one post request in DRF

I want to create two entries from one post request. One in the 'Dates' model and one in 'Other' model. Code corresponding to both models is shown below.
class Dates(models.Model):
booking_id = models.AutoField(primary_key=True)
timestamp = models.DateTimeField(auto_now_add=True)
feedback = models.CharField(max_length=8, default='no')
myself = models.BooleanField(default=True)
class Meta:
app_label = 'bookings'
Other is:
class Other(models.Model):
booking_id = models.OneToOneField(
'bookings.Dates',
null=False,
default=1,
primary_key=True,
on_delete=models.CASCADE
)
name = models.CharField(max_length=64)
phone_number = models.CharField(max_length=14)
email_id = models.EmailField(max_length=128)
class Meta:
app_label = 'bookings'
I have validated the data from Dates Serializer and created the object in 'Dates' table. Now, I want to use the generated 'booking_id' as the same 'booking_id' for 'Other' table. How can I validate serializer and create an object in the 'Other' table while maintaining the consistency?
Here with consistency, I mean: Either create objects in both the tables if no error occurs or don't create an object if any error occurs.
You can make use of writable nested serializers to achieve this. You need to define a serializer class for Other model, then your Dates serializer can look like this:
class DatesSerializer(serializers.ModelSerializer):
other = OtherSerializer()
class Meta:
model = Dates
fields = ('timestamp', 'feedback', 'myself', 'other')
def validate_other(self, value):
# Run validations for Other model here, either manually or through OtherSerializer's is_valid method. You won't have booking_id in value here though, take that into account when modelling your validation process
def validate_feedback(self, value):
# Run validations specific to feedback field here, if necessary. You can do this for all serializer fields
def validate(self, data):
# Run non-field specific validations for Dates here
def create(self, validated_data):
# At this point, validation for both models are run and passed
# Pop other model data from validated_data first
other_data = validated_data.pop('other')
# Create Dates instance
dates = Dates.objects.create(**validated_data)
# Create Other instance now
Other.objects.create(booking_id=dates, **other_data)
return dates
You can use the defaul CreateModelMixin of DRF here, all nested object logic is handled in the serializer.

Django rest framework : extend user model for customer - one to one field

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.

How do I set default field value to value of other field in a Django model?

If I have the following model in django;
class MyModel(models.Model):
name = models.CharField(max_length=50)
fullname = models.CharField(max_length=100,default=name)
How do I make the fullname field default to name? As it is right now, the fullname defaults to the string representation of the name CharField.
Example:
new MyModel(name='joel')
would yield 'joel' as both name and fullname, whereas
new MyModel(name='joel',fullname='joel karlsson')
would yield different name and fullname.
I wonder if you're better off doing this via a method on your model:
class MyModel(models.Model):
name = models.CharField(max_length=50)
fullname = models.CharField(max_length=100)
def display_name(self):
if self.fullname:
return self.fullname
return self.name
Perhaps, instead of display_name this should be your __unicode__ method.
If you really want to do what you've asked though, then you can't do this using the default - use the clean method on your form instead (or your model, if you're using new-fangled model validation (available since Django 1.2).
Something like this (for model validation):
class MyModel(models.Model):
name = models.CharField(max_length=50)
fullname = models.CharField(max_length=100,default=name)
def clean(self):
self.fullname=name
Or like this (for form validation):
class MyModelForm(ModelForm):
class Meta:
model = MyModel
def clean(self):
cleaned_data = self.cleaned_data
cleaned_data['fullname'] = cleaned_data['name']
return cleaned_data
How about making a migration with a default value, and then adding custom data migration to your migration file? Here's a complete migration file example:
from datetime import timedelta
from django.db import migrations, models
import django.utils.timezone
# noinspection PyUnusedLocal
def set_free_credits_added_on(apps, schema_editor):
# noinspection PyPep8Naming
UserProfile = apps.get_model('core', 'UserProfile')
for user_profile in UserProfile.objects.all():
user_profile.free_credits_added_on = user_profile.next_billing - timedelta(days=30)
user_profile.save()
# noinspection PyUnusedLocal
def do_nothing(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('core', '0078_auto_20171104_0659'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='free_credits_added_on',
# This default value is overridden in the following data migration code
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name='Free Credits Added On'
),
preserve_default=False,
),
migrations.RunPython(code=set_free_credits_added_on, reverse_code=do_nothing),
]
Here the free_credits_added_on field is set to 30 days before the existing next_billing field.