Пример #1
0
    def test_02(self):
        """Was written for :ticket:`488` (Kann Person nicht mehr von
        Organisation entfernen (delete mti child with siblings)).

        """
        Person = rt.models.contacts.Person
        Company = rt.models.contacts.Company
        Partner = rt.models.contacts.Partner

        # 1 : does delete_child work in normal situation?
        john = create(Person, first_name="John", last_name="Doe")
        as_partner = Partner.objects.get(pk=john.pk)
        delete_child(as_partner, Person)
        self.assertEqual(Person.objects.count(), 0)
        self.assertEqual(Partner.objects.count(), 1)
        as_partner.delete()

        # 2 : delete_child with an mti sibling
        john = create(Person, first_name="John", last_name="Doe")
        as_partner = Partner.objects.get(pk=john.pk)
        insert_child(as_partner, Company, full_clean=True)
        # this is the sitation:
        self.assertEqual(Person.objects.count(), 1)
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Company.objects.count(), 1)

        # the following failed before #488 was fixed:
        delete_child(as_partner, Company)
Пример #2
0
    def test_02(self):
        """Was written for :ticket:`488` (Kann Person nicht mehr von
        Organisation entfernen (delete mti child with siblings)).

        """
        Person = rt.modules.contacts.Person
        Company = rt.modules.contacts.Company
        Partner = rt.modules.contacts.Partner

        # 1 : does delete_child work in normal situation?
        john = create(Person, first_name="John", last_name="Doe")
        as_partner = Partner.objects.get(pk=john.pk)
        delete_child(as_partner, Person)
        self.assertEqual(Person.objects.count(), 0)
        self.assertEqual(Partner.objects.count(), 1)
        as_partner.delete()

        # 2 : delete_child with an mti sibling
        john = create(Person, first_name="John", last_name="Doe")
        as_partner = Partner.objects.get(pk=john.pk)
        insert_child(as_partner, Company, full_clean=True)
        # this is the sitation:
        self.assertEqual(Person.objects.count(), 1)
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Company.objects.count(), 1)

        # the following failed before #488 was fixed:
        delete_child(as_partner, Company)
Пример #3
0
    def run_on_row(self, obj, ar):

        if ar is not None:
            pre_remove_child.send(sender=obj,
                                  request=ar.request,
                                  child=self.child_model)
        mti.delete_child(obj, self.child_model, ar)
        ar.set_response(refresh=True)
Пример #4
0
 def run_on_row(self, obj, ar):
 
     if ar is not None:
         pre_remove_child.send(
             sender=obj, request=ar.request,
             child=self.child_model)
     mti.delete_child(obj, self.child_model, ar)
     ar.set_response(refresh=True)
Пример #5
0
    def swapclass(self, watcher, new_class, data):
        """
        Convert the watched object to a new_class instance and apply data accordingly.
        Caution: Here be dragons! See also :mod:`lino_welfare.tests.watchtim_tests`.
          
        """
        obj = watcher.watched
        old_class = obj.__class__
        assert old_class is not new_class
        newobj = None
        # ~ print 20130222, old_class, new_class
        if old_class is Partner:
            partner = obj
        else:
            partner = obj.partner_ptr
        if old_class is Client:
            # convert Client to Person, then continue as if old_class had been
            # Person
            dd.pre_remove_child.send(sender=obj, request=REQUEST, child=old_class)
            mti.delete_child(obj, old_class)
            newobj = obj = obj.person_ptr
            old_class = Person

        if old_class is new_class:
            return

        # ~ if new_class is not old_class and not issubclass(new_class,old_class):
        if not issubclass(new_class, old_class):
            dd.pre_remove_child.send(sender=obj, request=REQUEST, child=old_class)
            mti.delete_child(obj, old_class)
            newobj = obj = partner

        if new_class is Client:
            # create the Person if necessary:
            # ~ partner = obj.partner_ptr
            try:
                person = Person.objects.get(pk=partner.id)
            except Person.DoesNotExist:
                dd.pre_add_child.send(sender=partner, request=REQUEST, child=Person)
                person = mti.insert_child(partner, Person)
            self.applydata(person, data)
            self.validate_and_save(person)
            # create the Client
            newobj = obj = person

        if new_class is not Partner:
            dd.pre_add_child.send(sender=obj, request=REQUEST, child=new_class)
            newobj = mti.insert_child(obj, new_class)
        if newobj is not None:
            self.applydata(newobj, data)
            self.validate_and_save(newobj)
Пример #6
0
    def test01(self):
        from lino.modlib.users.choicelists import UserTypes
        User = rt.models.users.User
        Partner = rt.models.contacts.Partner
        Person = rt.models.contacts.Person
        Company = rt.models.contacts.Company
        Role = rt.models.contacts.Role
        Account = rt.models.sepa.Account
        Invoice = rt.models.vat.VatAccountInvoice
        Journal = rt.models.ledger.Journal
        VoucherTypes = rt.models.ledger.VoucherTypes
        JournalGroups = rt.models.ledger.JournalGroups

        u = User(username='******',
                 user_type=UserTypes.admin,
                 language="en")
        u.save()

        def createit():
            obj = Person(first_name="John", last_name="Doe")
            obj.full_clean()
            obj.save()
            pk = obj.pk
            return (obj, Partner.objects.get(pk=pk))

        #
        # If there are no vetos, user can ask to delete from any MTI form
        #
        pe, pa = createit()
        pe.delete()

        pe, pa = createit()
        pa.delete()

        #
        # Cascade-related objects (e.g. addresses) are deleted
        # independently of the polymorphic form which initiated
        # deletion.
        #

        def check_cascade(model):
            pe, pa = createit()
            obj = model.objects.get(pk=pa.pk)
            rel = Account(partner=pa, iban="AL32293653370340154130927280")
            rel.full_clean()
            rel.save()
            obj.delete()
            self.assertEqual(Account.objects.count(), 0)

        check_cascade(Partner)
        check_cascade(Person)

        #
        # Vetos of one form are deteced by all other forms.
        #
        def check_veto(obj, expected):
            try:
                obj.delete()
                self.fail("Failed to raise Warning({0})".format(expected))
            except Warning as e:
                self.assertEqual(str(e), expected)

        VKR = create_row(
            Journal,
            ref="VKR", name="VKR",
            voucher_type=VoucherTypes.get_for_model(Invoice),
            journal_group=JournalGroups.sales)
        
        pe, pa = createit()

        def check_vetos(obj, msg):
            m = obj.__class__
            obj.full_clean()
            obj.save()
            check_veto(pa, msg)
            check_veto(pe, msg)
            self.assertEqual(m.objects.count(), 1)
            obj.delete()
            self.assertEqual(m.objects.count(), 0)

        msg = "Cannot delete Partner Doe John because 1 Invoices refer to it."
        msg = "Kann Partner Doe John nicht löschen weil 1 Rechnungen darauf verweisen."
        check_vetos(Invoice(partner=pa, journal=VKR), msg)

        #
        # Having an invoice does not prevent from removing the Person
        # child of the partner.
        #
        
        invoice = create_row(Invoice, partner=pa, journal=VKR)
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Person.objects.count(), 1)
        self.assertEqual(Invoice.objects.count(), 1)

        delete_child(pa, Person)

        # tidy up:
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Person.objects.count(), 0)
        invoice.delete()
        pa.delete()
        self.assertEqual(Partner.objects.count(), 0)

        # But Lino refuses to remove the Person child if it has vetos.
        # For example if the person form is being used as a contact person.

        pe, pa = createit()
        co = create_row(Company, name="Test")
        create_row(Role, company=co, person=pe)
        msg = "[u'Cannot delete Partner Doe John because 1 Contact Persons refer to it.']"
        if six.PY2:
            msg = "[u'Kann Partner Doe John nicht l\\xf6schen weil 1 Kontaktpersonen darauf verweisen.']"
        else:
            msg = "['Kann Partner Doe John nicht löschen weil 1 Kontaktpersonen darauf verweisen.']"
        try:
            delete_child(pa, Person)
            self.fail("Expected ValidationError")
        except ValidationError as e:
            self.assertEqual(msg, str(e))
Пример #7
0
    def test_create_entry(self):
        ses = rt.login()

        Person = rt.models.app.Person
        Restaurant = rt.models.app.Restaurant
        Place = rt.models.app.Place
        Visit = rt.models.app.Visit
        Meal = rt.models.app.Meal

        # Create some initial data:

        Person(name="Alfred").save()
        Person(name="Bert").save()
        Person(name="Claude").save()
        Person(name="Dirk").save()
        r = Restaurant(id=1, name="First")
        r.save()
        for i in 1, 2:
            r.owners.add(Person.objects.get(pk=i))
        for i in 3, 4:
            r.cooks.add(Person.objects.get(pk=i))

        # Here is our data:

        lst = list(Person.objects.all())
        self.assertEqual(str(lst), "[<Person: Alfred>, <Person: Bert>, <Person: Claude>, <Person: Dirk>]")

        lst = list(Restaurant.objects.all())
        self.assertEqual(
            str(lst),
            "[Restaurant #1 ('First (owners=Alfred, Bert, cooks=Claude, Dirk)')]")
        
        x = list(Place.objects.all())
        self.assertEqual(
            str(x),
            "[Place #1 ('First (owners=Alfred, Bert)')]")


        """
        The :func:`delete_child` function
        ---------------------------------

        Imagine that a user of our application discovers that Restaurant #1
        isn't actually a `Restaurant`, it's just a `Place`.  They would like
        to "remove it's Restaurant data" from the database, but keep the
        `Place` data.  Especially the primary key (#1) and the related objects
        (the owners) should remain unchanged. But the cooks must be deleted
        since they exist only for restaurants.

        It seems that this is not trivial in Django (`How do you delete child
        class object without deleting parent class object?
        <http://stackoverflow.com/questions/9439730>`__).  That's why we wrote
        the :func:`delete_child` function.
        Here is how to "reduce" a Restaurant to a `Place` by 
        calling the :func:`delete_child` function:

        """

        from lino.utils.mti import delete_child

        p = Place.objects.get(id=1)
        delete_child(p, Restaurant)

        # The Place still exists, but no longer as a Restaurant:

        x = Place.objects.get(pk=1)
        self.assertEqual(
            str(x),
            "First (owners=Alfred, Bert)")

        try:
            list(Restaurant.objects.get(pk=1))
            self.fail("Expected DoesNotExist")
        except Restaurant.DoesNotExist:
            pass
            # Traceback (most recent call last):
        # ...
        # DoesNotExist: Restaurant matching query does not exist.

        """

        The :func:`insert_child` function
        ----------------------------------

        The opposite operation, "promoting a simple Place to a Restaurant", 
        is done using :func:`insert_child`.

        from lino.utils.mti import insert_child

        Let's first create a simple Place #2 with a single owner.
        """

        obj = Place(id=2, name="Second")
        obj.save()
        obj.owners.add(Person.objects.get(pk=2))
        obj.save()
        self.assertEqual(
            str(obj),
            "Second (owners=Bert)")

        # Now this Place becomes a Restaurant and hires 2 cooks:

        obj = insert_child(obj, Restaurant)
        for i in 3, 4:
            obj.cooks.add(Person.objects.get(pk=i))
        self.assertEqual(
            str(obj),
            "Second (owners=Bert, cooks=Claude, Dirk)")

        # If you try to promote a Person to a Restaurant, you'll get an exception:

        person = Person.objects.get(pk=2)
        try:
            insert_child(person, Restaurant).save()
            self.fail("Expected ValidationError")
        except ValidationError as e:
            self.assertEqual(
                str(e), "['A Person cannot be parent for a Restaurant']")

        """
        The :class:`EnableChild` virtual field 
        --------------------------------------

        This section shows how the :class:`EnableChild` virtual field is being 
        used by Lino, and thus is Lino-specific.


        After the above examples our database looks like this:

        """
         
        x = list(Person.objects.all())
        self.assertEqual(
            str(x),
            "[<Person: Alfred>, <Person: Bert>, <Person: Claude>, <Person: Dirk>]")
        x = list(Place.objects.all())
        self.assertEqual(
            str(x),
            "[Place #1 ('First (owners=Alfred, Bert)'), Place #2 ('Second (owners=Bert)')]")
        x = list(Restaurant.objects.all())
        self.assertEqual(
            str(x),
            "[Restaurant #2 ('Second (owners=Bert, cooks=Claude, Dirk)')]")

        # Let's take Place #1 and look at it.

        obj = Place.objects.get(pk=1)
        self.assertEqual(
            str(obj), "First (owners=Alfred, Bert)")

        # How to see whether a given Place is a Restaurant?

        x  = ""
        for i in Place.objects.all():
            x += "{0} -> {1}\n".format(i, i.get_mti_child('restaurant'))
        self.assertEqual(
            x, """\
First (owners=Alfred, Bert) -> None
Second (owners=Bert) -> Second (owners=Bert, cooks=Claude, Dirk)
""")

        # Let's promote First (currently a simple Place) to a Restaurant:

        x = insert_child(obj, Restaurant)
        # Restaurant #1 ('#1 (name=First, owners=Alfred, Bert, cooks=)')


        # And Second stops being a Restaurant:

        second = Place.objects.get(pk=2)
        delete_child(second, Restaurant)

        # This operation has removed the related Restaurant instance:

        try:
            Restaurant.objects.get(pk=2)
            self.fail("Expected DoesNotExist")
        except Restaurant.DoesNotExist:
            pass

        # And finally, rather to explain why Restaurants sometimes 
        # close and later reopen:

        bert = Person.objects.get(pk=2)
        second = Place.objects.get(pk=2)
        insert_child(second, Restaurant)
        # Restaurant #2 ('#2 (name=Second, owners=Bert, cooks=)')

        # Now we can see this place again as a Restaurant

        second = Restaurant.objects.get(pk=2)

        # And engage for example a new cook:

        second.cooks.add(bert)
        # second
        # Restaurant #2 ('#2 (name=Second, owners=Bert, cooks=Bert)')



        # Related objects
        # ---------------

        # Now let's have a more detailed look at what happens to the related 
        # objects (Person, Visit and Meal).

        # Bert, the owner of Restaurant #2 does two visits:

        second = Restaurant.objects.get(pk=2)
        Visit(purpose="Say hello", person=bert, place=second).save()
        Visit(purpose="Hang around", person=bert, place=second).save()
        x = list(second.visit_set.all())
        self.assertEqual(
            str(x),
            "[<Visit: Say hello visit by Bert at Second>, <Visit: Hang around visit by Bert at Second>]")

        # Claude and Dirk, now workless, still go to eat in restaurants:

        Meal(what="Fish",person=Person.objects.get(pk=3),restaurant=second).save()
        Meal(what="Meat",person=Person.objects.get(pk=4),restaurant=second).save()
        x = list(second.meal_set.all())
        self.assertEqual(
            str(x),
            "[<Meal: Claude eats Fish at Second>, <Meal: Dirk eats Meat at Second>]")

        # Now we reduce Second to a Place:

        second = Place.objects.get(pk=2)
        delete_child(second, Restaurant)

        # Restaurant #2 no longer exists:

        try:
            Restaurant.objects.get(pk=2)
            self.fail("Expected DoesNotExist")
        except Restaurant.DoesNotExist:
            pass
        
        # Note that `Meal` has :attr:`allow_cascaded_delete
        # <lino.core.model.Model.allow_cascaded_delete>` set to
        # `['restaurant']`, otherwise the above code would have raised a
        # ValidationError :message:`Cannot delete #2
        # (name=Second,owners=Bert,cooks=Bert) because 2 meals refer to it.` But
        # the meals have been deleted:

        self.assertEqual(Meal.objects.count(), 0)

        # Of course, #2 remains as a Place
        # The owner and visits have been taken over:

        second = Place.objects.get(pk=2)
        x = list(second.visit_set.all())
        self.assertEqual(
            str(x),
            "[<Visit: Say hello visit by Bert at Second>, <Visit: Hang around visit by Bert at Second>]")


        # The :func:`create_mti_child` function
        # -------------------------------------

        # This function is for rather internal use.  :ref:`Python dumps <dpy>`
        # generated by :class:`lino.utils.dpy.Serializer` use this function for
        # creating MTI children instances without having to lookup their parent.

        # .. currentmodule:: lino.utils.dpy

        # In a Python dump we are in a special situation: All Place instances
        # are being generated first, and in another step we are going to create
        # all the Restaurant instances.  So how can we create a Restaurant whose
        # Place already exists *without first having to do a lookup of the Place
        # record*?  That's why :func:`create_mti_child` was written for.

        obj = Place(id=3, name="Third")
        obj.save()
        obj.owners.add(Person.objects.get(pk=2))
        obj.save()
        self.assertEqual(
            str(obj),
            "Third (owners=Bert)")

        from lino.utils.dpy import create_mti_child
        obj = create_mti_child(Place, 3, Restaurant)

        # The return value is technically a normal model instance,
        # but whose `save` and `full_clean` methods have been 
        # patched: `full_clean` is overridden to do nothing, 
        # and `save` will call a "raw" save to avoid the 
        # need of a proper Place instance for that Restaurant.
        # The only thing you can do with it is to save it:

        obj.save()

        # The `save` and `full_clean` methods are the only methods that 
        # will be called by 
        # :class:`lino.utils.dpy.Deserializer`.

        # To test whether :func:`create_mti_child` did her job, 
        # we must re-read an instance:

        obj = Restaurant.objects.get(pk=3)
        self.assertEqual(
            str(obj),
            "Third (owners=Bert, cooks=)")

        # Note that :func:`create_mti_child` supports changing the
        # `name` although that field is defined in the Place model,
        # not in Restaurant. This feature was added 20170626 (#1926,
        # #1923). Before that date Lino raised an exception when you
        # specified a field of the parent model.  And before *that*
        # (until 20120930) this case was silently ignored for
        # backwards compatibility (`/blog/2011/1210`).

        obj = Place(id=4, name="Fourth")
        obj.save()
        ow = create_mti_child(Place, 4, Restaurant, name="A new name")
        ow.full_clean()
        ow.save()
        obj = Restaurant.objects.get(id=4)
        self.assertEqual(obj.name, "A new name")
Пример #8
0
    def test01(self):
        from lino.modlib.users.choicelists import UserTypes
        User = rt.modules.users.User
        Partner = rt.modules.contacts.Partner
        Person = rt.modules.contacts.Person
        Company = rt.modules.contacts.Company
        Role = rt.modules.contacts.Role
        Account = rt.modules.sepa.Account
        Invoice = rt.modules.vat.VatAccountInvoice
        Journal = rt.modules.ledger.Journal
        VoucherTypes = rt.modules.ledger.VoucherTypes
        JournalGroups = rt.modules.ledger.JournalGroups

        u = User(username='******',
                 profile=UserTypes.admin,
                 language="en")
        u.save()

        def createit():
            obj = Person(first_name="John", last_name="Doe")
            obj.full_clean()
            obj.save()
            pk = obj.pk
            return (obj, Partner.objects.get(pk=pk))

        #
        # If there are no vetos, user can ask to delete from any MTI form
        #
        pe, pa = createit()
        pe.delete()

        pe, pa = createit()
        pa.delete()

        #
        # Cascade-related objects (e.g. addresses) are deleted
        # independently of the polymorphic form which initiated
        # deletion.
        #

        def check_cascade(model):
            pe, pa = createit()
            obj = model.objects.get(pk=pa.pk)
            rel = Account(partner=pa, iban="AL32293653370340154130927280")
            rel.full_clean()
            rel.save()
            obj.delete()
            self.assertEqual(Account.objects.count(), 0)

        check_cascade(Partner)
        check_cascade(Person)

        #
        # Vetos of one form are deteced by all other forms.
        #
        def check_veto(obj, expected):
            try:
                obj.delete()
                self.fail("Failed to raise Warning({0})".format(expected))
            except Warning as e:
                self.assertEqual(str(e), expected)

        VKR = create(
            Journal,
            ref="VKR", name="VKR",
            voucher_type=VoucherTypes.get_for_model(Invoice),
            journal_group=JournalGroups.sales)
        
        pe, pa = createit()

        def check_vetos(obj, msg):
            m = obj.__class__
            obj.full_clean()
            obj.save()
            check_veto(pa, msg)
            check_veto(pe, msg)
            self.assertEqual(m.objects.count(), 1)
            obj.delete()
            self.assertEqual(m.objects.count(), 0)

        msg = "Cannot delete Partner Doe John because 1 Invoices refer to it."
        check_vetos(Invoice(partner=pa, journal=VKR), msg)

        #
        # Having an invoice does not prevent from removing the Person
        # child of the partner.
        #
        
        invoice = create(Invoice, partner=pa, journal=VKR)
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Person.objects.count(), 1)
        self.assertEqual(Invoice.objects.count(), 1)

        delete_child(pa, Person)

        # tidy up:
        self.assertEqual(Partner.objects.count(), 1)
        self.assertEqual(Person.objects.count(), 0)
        invoice.delete()
        pa.delete()
        self.assertEqual(Partner.objects.count(), 0)

        # But Lino refuses to remove the Person child if it has vetos.
        # For example if the person form is being used as a contact person.

        pe, pa = createit()
        co = create(Company, name="Test")
        create(Role, company=co, person=pe)
        msg = "[u'Cannot delete Partner Doe John because " \
              "1 Contact Persons refer to it.']"
        try:
            delete_child(pa, Person)
            self.fail("Expected ValidationError")
        except ValidationError as e:
            self.assertEqual(str(e), msg)