Tutorial: ldap3 Abstraction Layer - Writing data
################################################

Writing entries
---------------

Modifying data on an LDAP server is easy with the Abstraction Layer, just add a new Entry with its Attribute values (or change the Attribute
values of an existing Entry) and commit the pending changes to the DIT via the Writer cursor. You can obtain a Writer cursor in a number of ways:

* from a Reader Cursor, using the ``Writer.from_cursor()`` static method that populates the ``entries`` collection with a copy of the Entries
  from the Reader cursor.

* from a Search response, using the ``Writer.from_response()`` static method, that populates the ``entries`` collection with a copy of the Entries
  from the Search response.

* from a single Entry in a Reader cursor, using the ``entry_writable()`` method of the Entry, that returns a new Writable
  Entry (and also creates its Writer cursor).

* from a single Entry in a Search response, using the ``entry_writable()`` method of the Entry, that returns a new Writable
  Entry (and also creates its Writer cursor)

* as a new instance of the ldap3.abstract.Writer class, using ``Writer()`` that creates a new Writer cursor with an empty ``entries``
  collection. With this cursor you can only create new Entries.

Let's obtain a Writer cursor from the inetOrgPerson Reader we used in the previous chapter::

    >>> r = Reader(conn, obj_inetorgperson, 'ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org')
    >>> r.search()
    >>> w = Writer.from_cursor(r)
    >>> w
    CURSOR : Writer
    CONN   : ldap://ipa.demo1.freeipa.org:389 - cleartext - user: uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - not lazy - bound - open - <local: 10.3.9.227:29872 - remote: 209.132.178.99:389> - tls not started - listening - SyncStrategy - internal decoder
    DEFS   : ['inetOrgPerson'] [audio, businessCategory, carLicense, cn, departmentNumber, description, destinationIndicator, displayName, employeeNumber, employeeType, facsimileTelephoneNumber, givenName, homePhone, homePostalAddress, initials, internationalISDNNumber, jpegPhoto, l, labeledURI, mail, manager, mobile, o, objectClass, ou, pager, photo, physicalDeliveryOfficeName, postOfficeBox, postalAddress, postalCode, preferredDeliveryMethod, preferredLanguage, registeredAddress, roomNumber, secretary, seeAlso, sn, st, street, telephoneNumber, teletexTerminalIdentifier, telexNumber, title, uid, userCertificate, userPKCS12, userPassword, userSMIMECertificate, x121Address, x500UniqueIdentifier]
    ATTRS  : ['audio', 'businessCategory', 'carLicense', 'cn', 'departmentNumber', 'description', 'destinationIndicator', 'displayName', 'employeeNumber', 'employeeType', 'facsimileTelephoneNumber', 'givenName', 'homePhone', 'homePostalAddress', 'initials', 'internationalISDNNumber', 'jpegPhoto', 'l', 'labeledURI', 'mail', 'manager', 'mobile', 'o', 'objectClass', 'ou', 'pager', 'photo', 'physicalDeliveryOfficeName', 'postOfficeBox', 'postalAddress', 'postalCode', 'preferredDeliveryMethod', 'preferredLanguage', 'registeredAddress', 'roomNumber', 'secretary', 'seeAlso', 'sn', 'st', 'street', 'telephoneNumber', 'teletexTerminalIdentifier', 'telexNumber', 'title', 'uid', 'userCertificate', 'userPKCS12', 'userPassword', 'userSMIMECertificate', 'x121Address', 'x500UniqueIdentifier']
    ENTRIES: 3 [executed at: 2016-11-09T14:24:49.374675]

Entries in a Writer cursor are standard Python object, so you can modify them with standard Python code::

    >>> w[0]
    DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable - READ TIME: 2016-11-09T14:26:03.866351
        cn: b.young
        departmentNumber: DEV
        givenName: Beatrix
        objectClass: inetOrgPerson
                     organizationalPerson
                     person
                     top
        sn: Young
        telephoneNumber: 1111

The entry is in **Writable** status now, so you can try to update some values. All modifications are stored in memory until committed or the entry
is returned to its original values::

    >>> w[0].sn += 'Smith'  # Add 'Smith' value from the sn
    >>> w[0].sn += 'Johnson'  # Add 'Johnson' value from the sn
    >>> w[0].sn -= 'Young'  # remove the 'Young' value from the sn

Now let's revise the modifications we have requested::

    >>> w[0].entry_changes
    OrderedDict([('sn', [('MODIFY_ADD', ['Smith']), ('MODIFY_ADD', ['Johnson']), ('MODIFY_DELETE', ['Young'])])])

Modifications to an Entry are stored in a way (OrderedDict) that preserves the insertion sequence. This can be helpful with specific LDAP
operations that request that an attribute is modified before an other one in the same LDAP operation

We made a typo so discard the changes and insert the correct values:

    >>> w[0].sn.discard()
    >>> w[0].sn += ['Smith', 'Johnson']  # add a list of values
    >>> w[0].sn -= 'Young'  # remove the 'Young' value from the sn
    >>> w[0]
    DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable, Pending changes - READ TIME: 2016-11-09T14:30:43.181520
        cn: b.young
        departmentNumber: DEV
        givenName: Beatrix
        objectClass: inetOrgPerson
                     organizationalPerson
                     person
                     top
        sn: Young
            CHANGES: [('MODIFY_ADD', ['Smith', 'Johnson']), ('MODIFY_DELETE', ['Young'])]
        telephoneNumber: 1111

Entry status is set to *Writable, Pending changes*, this means that mandatory Attributes are set and the Entry can be written in the DIT::

    >>> w.commit()  # commit all entries with pending changes
    >>> w[0]
    DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Writable, Committed - READ TIME: 2016-11-09T14:32:14.377498
        cn: b.young
        departmentNumber: DEV
        givenName: Beatrix
        objectClass: inetOrgPerson
                     organizationalPerson
                     person
                     top
        sn: Smith
            Johnson
        telephoneNumber: 1111

Entry has been written on the DIT and its state is now *Writable, Committed*. If you look at the original Entry in the Reader you will find that
it's been updated with the new values::

    >>> r[0]
    DN: cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2016-11-09T14:32:14.377498
        cn: b.young
        departmentNumber: DEV
        givenName: Beatrix
        objectClass: inetOrgPerson
                     organizationalPerson
                     person
                     top
        sn: Smith
            Johnson
        telephoneNumber: 1111

Refreshing of the original Entry is triggered only if both cursors are using the same Server object. If you use the Writer cursor to copy Entries
to another LDAP server refreshing of the original Entry is not executed.

For specific types (boolean, integers and dates) you can set the value to the relevant Python type. The ldap3 library will perform the necessary
conversion to the value expected from the LDAP server.
