TheNone
TheNone

Reputation: 5802

Copy Model Object From a Model To Another In Django

I have to model. I want to copy model object from a model to another: Model2 is copy of Model1 (this models has too many m2m fields) Model1:

class Profile(models.Model):
      user = models.OneToOneField(User)
      car = models.ManyToManyField(Car)
      job = models.ManyToManyField(Job)
      .
      .

This is a survey application. I want to save user's profile when he/she attends the survey (because he can edit profile after survey) I have created another model to save user profile when he takes survey (Im not sure its the right way)

class SurveyProfile(models.Model):
      user = models.OneToOneField(SurveyUser) #this is another model that takes survey users
      car = models.ManyToManyField(Car)
      job = models.ManyToManyField(Job)

How can I copy user profile from Profile to SurveyProfile.

Thanks in advance

Upvotes: 7

Views: 11549

Answers (6)

blueSurfer
blueSurfer

Reputation: 5821

I came across something similar but I needed to check also if ForeignKey fields have compatible models. I end up with the following method:

def copy_object(obj, model):
    kwargs = dict()
    for field in model._meta.fields:
        if hasattr(obj, field.name) and not field.primary_key:
            if field.remote_field is not None:
                obj_field = obj._meta.get_field(field.name)
                if obj_field.remote_field != field.remote_field:
                    continue
            kwargs[field.name] = getattr(obj, field.name)
    return model(**kwargs)

Upvotes: 0

Steve Jalim
Steve Jalim

Reputation: 12195

deepcopy etc won't work because the classes/Models are different.

If you're certain that SurveyProfile has the all of the fields present in Profile*, this should work (not tested it):

for field in instance_of_model_a._meta.fields:
    if field.primary_key == True:
        continue  # don't want to clone the PK
    setattr(instance_of_model_b, field.name, getattr(instance_of_model_a, field.name))
instance_of_model_b.save()

* (in which case, I suggest you make an abstract ProfileBase class and inherit that as a concrete class for Profile and SurveyProfile, but that doesn't affect what I've put above)

Upvotes: 11

chander
chander

Reputation: 2167

This is how I do it (note: this is in Python3, you might need to change things - get rid of the dictionary comprehension - if you are using python 2):

def copy_instance_kwargs(src, exclude_pk=True, excludes=[]):
    """
    Generate a copy of a model using model_to_dict, then make sure
    that all the FK references are actually proper FK instances.  
    Basically, we return a set of kwargs that may be used to create
    a new instance of the same model - or copy from one model
    to another.

    The resulting dictionary may be used to create a new instance, like so:

    src_dict = copy_instance_kwargs(my_instance)
    ModelClass(**src_dict).save()

    :param src: Instance to copy
    :param exclude_pk: Exclude the PK of the model, to ensure new records are copies.
    :param excludes: A list of fields to exclude (must be a mutable iterable) from the copy. (date_modified, for example)
    """
    # Exclude the PK of the model, since we probably want to make a copy.
    if exclude_pk:
        excludes.append(src._meta.pk.attname)
    src_dict = model_to_dict(src, exclude=excludes)
    fks={k: getattr(src, k) for k in src_dict.keys() if 
         isinstance(getattr(src, k, None), models.Model) }
    src_dict.update(fks)
    return src_dict

Upvotes: 0

Stuart Axon
Stuart Axon

Reputation: 1874

Here's the function I've been using, it builds on model_to_dict. Model_to_dict just returns the ids of foreign keys + not their instances, so for those I replace them with the model itself.

def update_model(src, dest):
    """
    Update one model with the content of another.

    When it comes to Foreign Keys, they need to be
    encoded using models and not the IDs as
    returned from model_to_dict.

    :param src: Source model instance.
    :param dest: Destination model instance.
    """
    src_dict = model_to_dict(src, exclude="id")
    for k, v in src_dict.iteritems():
        if isinstance(v, long):
            m = getattr(src, k, None)
            if isinstance(m, models.Model):
                setattr(dest, k, m)
                continue

        setattr(dest, k, v)

Upvotes: 2

Zen
Zen

Reputation: 9

So, if I'm interpreting your problem correctly, you have an old model (Profile), and you're trying to replace it with the new model SurveyProfile. Given the circumstances, you may want to consider using a database migration tool like South in the long run. For now, you can run a script in the Django shell (manage.py shell):

from yourappname.models import *
for profile in Profile.objects.all():
    survey_profile = SurveyProfile()
    # Assuming SurveyUser has user = ForeignKey(User)...
    survey_profile.user = SurveyUser.objects.get(user=profile.user)
    survey_profile.car = profile.car
    survey_profile.job = profile.job
    survey_profile.save()

Using South

If this project needs to be maintained and updated in the long term, I would highly recommend using a database migration package like South, which will let you modify fields on a Model, and migrate your database painlessly.

For example, you suggest that your original model had too many ManyToManyFields present. With South, you:

  1. Delete the fields from the model.
  2. Auto-generate a schema migration.
  3. Apply the migration.

This allows you to reuse all of your old code without changing your model names or mucking with the database.

Upvotes: -1

James R
James R

Reputation: 4656

I'm having a tough time understanding what you wrote above, consequently I'm not 100% certain if this will work, but what I think I would do is something like this, if I'm understanding you right:

class Model2Form(ModelForm):
    class Meta:
        model = models.Model2

and then

f = Model2Form(**m1.__dict__)
if f.is_valid():
    f.save()

But I think this looks more like poor database design then anything, without seeing the entire model1 I can't be certain. But, in any event, I'm not sure why you want to do that anyway, when you can simply use inheritance at the model level, or something else to get the same behavior.

Upvotes: 3

Related Questions