7

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

2
  • Is Model1 the same class as Model2? ie, you're talking about two instances of the same Model, or two Models which happen to have the same fields? Commented May 30, 2012 at 13:53
  • 1
    Have you thought about adding a foreign key field to the SurveyProfile model to the Profile model or the User model? Commented May 30, 2012 at 16:21

6 Answers 6

11

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)

Sign up to request clarification or add additional context in comments.

2 Comments

You might want to check that the field.name is not id, you don't want to copy the id.
I've used this solution before. However, I would check field.primary_key == True rather than field.name == "id"`. It makes it more explicit.
3

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.

1 Comment

The question makes no mention of forms or a desire to use any, but rather how to copy a Profile object to the SurveyProfile model. I'm concerned that introducing forms into the process unnecesarily complicates the matter,
2

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)

Comments

0

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

Comments

0

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)

Comments

-1

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.

3 Comments

Thanks zen, But I dont want delete the profile. I need 2 profile option for example : when user attend survey : city = newyork , after 2 weeks, he changes her profile : city = paris . We may see first profile option (SurveyProfile()) . Many Thanks
I see. Let me try reinterpreting it: you want the user's profile to be updated every time he takes a survey, but you also want to save the results of every survey separately from the user's profile?
,Our project manager tell us to do that. But there is not a good way because there is 40 manytomany field in Profile. Is there a better way to do that? Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.