2010-12-13 2 views
11

(Django 1.1) У меня есть модель проекта, которая отслеживает ее членов, используя поле m2m. Это выглядит следующим образом:Django - Как сохранить данные m2m через сигнал post_save?

class Project(models.Model): 
    members = models.ManyToManyField(User) 
    sales_rep = models.ForeignKey(User) 
    sales_mgr = models.ForeignKey(User) 
    project_mgr = models.ForeignKey(User) 
    ... (more FK user fields) ... 

Когда проект создан, выбранный sales_rep, sales_mgr, project_mgr и т.д. User s добавлены к членам, чтобы сделать его легче отслеживать разрешения проекта. До сих пор этот подход работал очень хорошо.

Проблема, с которой я имею дело сейчас, заключается в том, как обновить членство в проекте, когда один из полей FK User обновлен через администратора. Я пробовал различные решения этой проблемы, но чистый подход казался post_save сигнал, как следующее:

def update_members(instance, created, **kwargs): 
    """ 
    Signal to update project members 
    """ 
    if not created: #Created projects are handled differently 
     instance.members.clear() 

     members_list = [] 
     if instance.sales_rep: 
      members_list.append(instance.sales_rep) 
     if instance.sales_mgr: 
      members_list.append(instance.sales_mgr) 
     if instance.project_mgr: 
      members_list.append(instance.project_mgr) 

     for m in members_list: 
      instance.members.add(m) 
signals.post_save.connect(update_members, sender=Project) 

Однако Project все еще имеет те же элементы, даже если я изменить один из полей с помощью админ! Я успешно обновлял поля m2m, используя свои собственные представления в других проектах, но мне никогда не приходилось так хорошо играть с администратором.

Есть ли другой подход, который я должен использовать вместо сообщения post_save для обновления членства? Заранее спасибо за вашу помощь!

UPDATE:

Просто чтобы прояснить, сигнал post_save работает правильно, когда я сохраняю свою собственную форму в передней части (старые члены удаляются, и добавлены новые). Однако сигнал post_save НЕ работает правильно, когда я сохраняю проект через администратора (члены остаются неизменными).

Я думаю, что диагноз Питера Роуэлла правилен в этой ситуации. Если я удаляю поле «члены» из формы администратора, сигнал post_save работает правильно. Когда поле включено, оно сохраняет старые элементы на основе значений, присутствующих в форме во время сохранения. Независимо от того, какие изменения я внес в поле m2m членов при сохранении проекта (будь то сигнал или пользовательский метод сохранения), он всегда будет перезаписан членами, которые присутствовали в форме до сохранения. Спасибо что подметил это!

+2

Я не знаю, если это ваша проблема, но у меня есть шестое чувство, что вы можете быть запущены в артефакт, как код формы обновляет информацию m2m. В основном они сначала сохраняют основной объект, затем устанавливают значения m2m, сначала очищая их все, а затем устанавливая их на основе имеющихся значений * в форме *. Это происходит * после * сохранения() на основном объекте, поэтому все, что вы делаете в save() или на основе сигнала 'post_save', сначала выполняется, а затем * отменяется *. Это находится в 'django.forms.models.save_instance()'. Было бы неплохо, если бы был сигнал 'after_form_save'. –

+0

Спасибо, Питер! Я считаю, что ваш диагноз правильный. Я обновил свой оригинальный пост, чтобы включить эту информацию. –

+0

Питер прав. У меня была та же проблема, и я нашел обходное решение, но это не так, как сигнал «after_form_save»: http://stackoverflow.com/questions/3652585/simple-django-form-model-save-question –

ответ

4

Я не вижу ничего плохого в вашем коде, но я в замешательстве, почему вы думаете, что администратор должен работать независимо от любого другого приложения.

Однако, я должен сказать, что ваша структура модели неверна. Я думаю, вам нужно избавиться от всех полей ForeignKey и просто иметь ManyToMany, но использовать сквозную таблицу для отслеживания ролей.

class Project(models.Model): 
    members = models.ManyToManyField(User, through='ProjectRole') 

class ProjectRole(models.Model): 
    ROLES = (
     ('SR', 'Sales Rep'), 
     ('SM', 'Sales Manager'), 
     ('PM', 'Project Manager'), 
    ) 
    project = models.ForeignKey(Project) 
    user = models.ForeignKey(User) 
    role = models.CharField(max_length=2, choices=ROLES) 
+0

Я согласен с тем, что структура модели должна быть улучшена, но я работаю со старой реализацией и стараюсь максимально использовать ее. В настоящее время я не готов перенести систему на эту новую структуру, но буду помнить о будущем. Благодарю. –

6

Имея ту же проблему, мое решение заключается в использовании сигнала m2m_changed. Вы можете использовать его в двух местах, как в следующем примере.

Администратор по экономии будет продолжаться до:

  • сохранить модель поля
  • испускают post_save сигнал
  • для каждого M2M:
    • испускать pre_clear
    • ясно соотношение
    • emit post_clear
    • испускают pre_add
    • снова
    • Заполнить
    • испускают post_add

Вот вам простой пример, который изменяет содержание сохраненных данных перед тем как сохранить его.

class MyModel(models.Model): 

    m2mfield = ManyToManyField(OtherModel) 

    @staticmethod 
    def met(sender, instance, action, reverse, model, pk_set, **kwargs): 
     if action == 'pre_add': 
      # here you can modify things, for instance 
      pk_set.intersection_update([1,2,3]) 
      # only save relations to objects 1, 2 and 3, ignoring the others 
     elif action == 'post_add': 
      print pk_set 
      # should contain at most 1, 2 and 3 

m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through) 

Вы также можете слушать pre_remove, post_remove, pre_clear и post_clear. В моем случае я использую их, чтобы отфильтровать один список («активные вещи») в пределах содержимого другой («Enabled вещей») независимо от того, в котором сохраняются списки:

def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs): 
    """ Ensures that the active services are a subset of the enabled ones. 
    """ 
    if action == 'pre_add' and sender == Account.active_services.through: 
     # remove from the selection the disabled ones 
     pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True)) 
    elif action == 'pre_clear' and sender == Account.enabled_services.through: 
     # clear everything 
     instance._cache_active_services = list(instance.active_services.values_list('id', flat=True)) 
     instance.active_services.clear() 
    elif action == 'post_add' and sender == Account.enabled_services.through: 
     _cache_active_services = getattr(instance, '_cache_active_services', None) 
     if _cache_active_services: 
      instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services))) 
      delattr(instance, '_cache_active_services') 
    elif action == 'pre_remove' and sender == Account.enabled_services.through: 
     # de-default any service we are disabling 
     instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set))) 

Если «включено» они обновляются (удаляются и удаляются + добавляются обратно, как и в админе), тогда «активные» кэшируются и очищаются в первом проходе («pre_clear»), а затем добавляются обратно из кэша после второго прохода («post_add») ,

Хитрость заключалась в том, чтобы обновить один список на сигналах m2m_changed другого.

+0

Ты спас мой день! Благодаря :) –

0

Я застрял в ситуации, когда мне нужно было найти последний элемент из набора элементов, подключенных к модели через m2m_field.

После ответа SAVERIO, в следующем коде решить мою проблему:

def update_item(sender, instance, action, **kwargs): 
    if action == 'post_add': 
     instance.related_field = instance.m2m_field.all().order_by('-datetime')[0] 
     instance.save() 

m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through) 

 Смежные вопросы

  • Нет связанных вопросов^_^