U
    PN^H                     @   s6  d Z ddlmZmZmZmZ ddlmZ ddl	m
Z
mZmZ dd Zdd Zd	d
 Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd  Zd!d" Zd#d$ Zd%d& Zd'd( Zd)d* Zd+d, Z d-d. Z!d/d0 Z"d1d2 Z#d3d4 Z$d5d6 Z%G d7d8 d8e&Z'd>d:d;Z(ed9d9d9d9fd<d=Z)d9S )?a7  
Form generation utilities for App Engine's ``db.Model`` class.

The goal of ``model_form()`` is to provide a clean, explicit and predictable
way to create forms based on ``db.Model`` classes. No malabarism or black
magic should be necessary to generate a form for models, and to add custom
non-model related fields: ``model_form()`` simply generates a form class
that can be used as it is, or that can be extended directly or even be used
to create other forms using ``model_form()``.

Example usage:

.. code-block:: python

   from google.appengine.ext import db
   from tipfy.ext.model.form import model_form

   # Define an example model and add a record.
   class Contact(db.Model):
       name = db.StringProperty(required=True)
       city = db.StringProperty()
       age = db.IntegerProperty(required=True)
       is_admin = db.BooleanProperty(default=False)

   new_entity = Contact(key_name='test', name='Test Name', age=17)
   new_entity.put()

   # Generate a form based on the model.
   ContactForm = model_form(Contact)

   # Get a form populated with entity data.
   entity = Contact.get_by_key_name('test')
   form = ContactForm(obj=entity)

Properties from the model can be excluded from the generated form, or it can
include just a set of properties. For example:

.. code-block:: python

   # Generate a form based on the model, excluding 'city' and 'is_admin'.
   ContactForm = model_form(Contact, exclude=('city', 'is_admin'))

   # or...

   # Generate a form based on the model, only including 'name' and 'age'.
   ContactForm = model_form(Contact, only=('name', 'age'))

The form can be generated setting field arguments:

.. code-block:: python

   ContactForm = model_form(Contact, only=('name', 'age'), field_args={
       'name': {
           'label': 'Full name',
           'description': 'Your name',
       },
       'age': {
           'label': 'Age',
           'validators': [validators.NumberRange(min=14, max=99)],
       }
   })

The class returned by ``model_form()`` can be used as a base class for forms
mixing non-model fields and/or other model forms. For example:

.. code-block:: python

   # Generate a form based on the model.
   BaseContactForm = model_form(Contact)

   # Generate a form based on other model.
   ExtraContactForm = model_form(MyOtherModel)

   class ContactForm(BaseContactForm):
       # Add an extra, non-model related field.
       subscribe_to_news = f.BooleanField()

       # Add the other model form as a subform.
       extra = f.FormField(ExtraContactForm)

The class returned by ``model_form()`` can also extend an existing form
class:

.. code-block:: python

   class BaseContactForm(Form):
       # Add an extra, non-model related field.
       subscribe_to_news = f.BooleanField()

   # Generate a form based on the model.
   ContactForm = model_form(Contact, base_class=BaseContactForm)

    )Form
validatorswidgetsfields)	iteritems)GeoPtPropertyFieldReferencePropertyFieldStringListPropertyFieldc                 C   s"   | d  tjdd tjf | S )zd
    Returns a ``TextField``, applying the ``db.StringProperty`` length limit
    of 500 bytes.
    r     max)appendr   lengthfZ	TextField)kwargs r   =/tmp/pip-install-o1yuzyg2/WTForms/wtforms/ext/appengine/db.pyget_TextFieldc   s    r   c                 C   s(   t jddd}| d | tjf | S )z\
    Returns an ``IntegerField``, applying the ``db.IntegerProperty`` range
    limits.
    l         l    minr   r   )r   NumberRanger   r   IntegerField)r   vr   r   r   get_IntegerFieldl   s    r   c                 C   s4   |j r(|d tjdd tjf |S t|S dS )z1Returns a form field for a ``db.StringProperty``.r   r
   r   N)	multiliner   r   r   r   TextAreaFieldr   modelpropr   r   r   r   convert_StringPropertyv   s    r   c                 C   s   t |S )z5Returns a form field for a ``db.ByteStringProperty``.r   r   r   r   r   convert_ByteStringProperty   s    r!   c                 C   s   t jf |S )z2Returns a form field for a ``db.BooleanProperty``.)r   ZBooleanFieldr   r   r   r   convert_BooleanProperty   s    r"   c                 C   s   t |S )z2Returns a form field for a ``db.IntegerProperty``.)r   r   r   r   r   convert_IntegerProperty   s    r#   c                 C   s   t jf |S )z0Returns a form field for a ``db.FloatProperty``.)r   Z
FloatFieldr   r   r   r   convert_FloatProperty   s    r$   c                 C   s(   |j s|jrdS |dd tjf |S )z3Returns a form field for a ``db.DateTimeProperty``.Nformatz%Y-%m-%d %H:%M:%Sauto_nowauto_now_add
setdefaultr   ZDateTimeFieldr   r   r   r   convert_DateTimeProperty   s    r*   c                 C   s(   |j s|jrdS |dd tjf |S )z/Returns a form field for a ``db.DateProperty``.Nr%   z%Y-%m-%d)r'   r(   r)   r   Z	DateFieldr   r   r   r   convert_DateProperty   s    r+   c                 C   s(   |j s|jrdS |dd tjf |S )z/Returns a form field for a ``db.TimeProperty``.Nr%   z%H:%M:%Sr&   r   r   r   r   convert_TimeProperty   s    r,   c                 C   s   dS )z/Returns a form field for a ``db.ListProperty``.Nr   r   r   r   r   convert_ListProperty   s    r-   c                 C   s
   t f |S )z5Returns a form field for a ``db.StringListProperty``.)r	   r   r   r   r   convert_StringListProperty   s    r.   c                 C   s$   |j |d< |d|j  tf |S )z4Returns a form field for a ``db.ReferenceProperty``.reference_classZallow_blank)r/   r)   requiredr   r   r   r   r   convert_ReferenceProperty   s    
r1   c                 C   s   dS )z8Returns a form field for a ``db.SelfReferenceProperty``.Nr   r   r   r   r   convert_SelfReferenceProperty   s    r2   c                 C   s   dS )z/Returns a form field for a ``db.UserProperty``.Nr   r   r   r   r   convert_UserProperty   s    r3   c                 C   s   t jf |S )z/Returns a form field for a ``db.BlobProperty``.)r   Z	FileFieldr   r   r   r   convert_BlobProperty   s    r4   c                 C   s   t jf |S )z/Returns a form field for a ``db.TextProperty``.)r   r   r   r   r   r   convert_TextProperty   s    r5   c                 C   s   t |S )z3Returns a form field for a ``db.CategoryProperty``.r    r   r   r   r   convert_CategoryProperty   s    r6   c                 C   s   |d  t  t|S )z/Returns a form field for a ``db.LinkProperty``.r   )r   r   urlr   r   r   r   r   convert_LinkProperty   s    r8   c                 C   s   |d  t  t|S )z0Returns a form field for a ``db.EmailProperty``.r   )r   r   emailr   r   r   r   r   convert_EmailProperty   s    r:   c                 C   s
   t f |S )z0Returns a form field for a ``db.GeoPtProperty``.)r   r   r   r   r   convert_GeoPtProperty   s    r;   c                 C   s   dS )z-Returns a form field for a ``db.IMProperty``.Nr   r   r   r   r   convert_IMProperty   s    r<   c                 C   s   t |S )z6Returns a form field for a ``db.PhoneNumberProperty``.r    r   r   r   r   convert_PhoneNumberProperty   s    r=   c                 C   s   t |S )z8Returns a form field for a ``db.PostalAddressProperty``.r    r   r   r   r   convert_PostalAddressProperty   s    r>   c                 C   s$   |d  tjddd tjf |S )z1Returns a form field for a ``db.RatingProperty``.r   r   d   r   )r   r   r   r   r   r   r   r   r   convert_RatingProperty   s    r@   c                   @   sd   e Zd ZdZeeeeee	e
eeeeeeeeeeeeeeeedZedddgZdddZd	d
 ZdS )ModelConvertera  
    Converts properties from a ``db.Model`` class to form fields.

    Default conversions between properties and fields:

    +====================+===================+==============+==================+
    | Property subclass  | Field subclass    | datatype     | notes            |
    +====================+===================+==============+==================+
    | StringProperty     | TextField         | unicode      | TextArea         |
    |                    |                   |              | if multiline     |
    +--------------------+-------------------+--------------+------------------+
    | ByteStringProperty | TextField         | str          |                  |
    +--------------------+-------------------+--------------+------------------+
    | BooleanProperty    | BooleanField      | bool         |                  |
    +--------------------+-------------------+--------------+------------------+
    | IntegerProperty    | IntegerField      | int or long  |                  |
    +--------------------+-------------------+--------------+------------------+
    | FloatProperty      | TextField         | float        |                  |
    +--------------------+-------------------+--------------+------------------+
    | DateTimeProperty   | DateTimeField     | datetime     | skipped if       |
    |                    |                   |              | auto_now[_add]   |
    +--------------------+-------------------+--------------+------------------+
    | DateProperty       | DateField         | date         | skipped if       |
    |                    |                   |              | auto_now[_add]   |
    +--------------------+-------------------+--------------+------------------+
    | TimeProperty       | DateTimeField     | time         | skipped if       |
    |                    |                   |              | auto_now[_add]   |
    +--------------------+-------------------+--------------+------------------+
    | ListProperty       | None              | list         | always skipped   |
    +--------------------+-------------------+--------------+------------------+
    | StringListProperty | TextAreaField     | list of str  |                  |
    +--------------------+-------------------+--------------+------------------+
    | ReferenceProperty  | ReferencePropertyF| db.Model     |                  |
    +--------------------+-------------------+--------------+------------------+
    | SelfReferenceP.    | ReferencePropertyF| db.Model     |                  |
    +--------------------+-------------------+--------------+------------------+
    | UserProperty       | None              | users.User   | always skipped   |
    +--------------------+-------------------+--------------+------------------+
    | BlobProperty       | FileField         | str          |                  |
    +--------------------+-------------------+--------------+------------------+
    | TextProperty       | TextAreaField     | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | CategoryProperty   | TextField         | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | LinkProperty       | TextField         | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | EmailProperty      | TextField         | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | GeoPtProperty      | TextField         | db.GeoPt     |                  |
    +--------------------+-------------------+--------------+------------------+
    | IMProperty         | None              | db.IM        | always skipped   |
    +--------------------+-------------------+--------------+------------------+
    | PhoneNumberProperty| TextField         | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | PostalAddressP.    | TextField         | unicode      |                  |
    +--------------------+-------------------+--------------+------------------+
    | RatingProperty     | IntegerField      | int or long  |                  |
    +--------------------+-------------------+--------------+------------------+
    | _ReverseReferenceP.| None              | <iterable>   | always skipped   |
    +====================+===================+==============+==================+
    )ZStringPropertyZByteStringPropertyBooleanPropertyZIntegerPropertyZFloatPropertyZDateTimePropertyZDatePropertyZTimePropertyListPropertyStringListPropertyZReferencePropertyZSelfReferencePropertyZUserPropertyZBlobPropertyZTextPropertyZCategoryPropertyZLinkPropertyZEmailPropertyZGeoPtPropertyZ
IMPropertyZPhoneNumberPropertyZPostalAddressPropertyZRatingPropertyrC   rD   rB   Nc                 C   s   |p| j | _dS )z
        Constructs the converter, setting the converter callables.

        :param converters:
            A dictionary of converter callables for each property type. The
            callable must accept the arguments (model, prop, kwargs).
        N)default_converters
converters)selfrF   r   r   r   __init__Y  s    zModelConverter.__init__c                 C   s   t |j}|jdd | g d}|r6|| |jrX|| jkrX|d 	t
  |jrd|krzdd |jD |d< tjf |S | j|d}|dk	r||||S dS )	aD  
        Returns a form field for a single model property.

        :param model:
            The ``db.Model`` class that contains the property.
        :param prop:
            The model property: a ``db.Property`` instance.
        :param field_args:
            Optional keyword arguments to construct the field.
        _ )labeldefaultr   r   choicesc                 S   s   g | ]}||fqS r   r   ).0r   r   r   r   
<listcomp>}  s     z*ModelConverter.convert.<locals>.<listcomp>N)type__name__namereplacetitledefault_valueupdater0   NO_AUTO_REQUIREDr   r   rM   r   ZSelectFieldrF   get)rG   r   r   
field_argsZprop_type_namer   	converterr   r   r   convertc  s     

zModelConverter.convert)N) rQ   
__module____qualname____doc__r   r!   r"   r#   r$   r*   r+   r,   r-   r.   r1   r2   r3   r4   r5   r6   r8   r:   r;   r<   r=   r>   r@   rE   	frozensetrW   rH   r[   r   r   r   r   rA      s8   >

rA   Nc           
         s   |pt  }|pi }|  }tt|dd d}tdd |D |r\tfdd|D n rvt fddD i }D ],}|| || ||}	|	dk	r~|	||< q~|S )	a  
    Extracts and returns a dictionary of form fields for a given
    ``db.Model`` class.

    :param model:
        The ``db.Model`` class to extract fields from.
    :param only:
        An optional iterable with the property names that should be included in
        the form. Only these properties will have fields.
    :param exclude:
        An optional iterable with the property names that should be excluded
        from the form. All other properties will have fields.
    :param field_args:
        An optional dictionary of field names mapping to a keyword arguments
        used to construct each field object.
    :param converter:
        A converter to generate the fields based on the model properties. If
        not set, ``ModelConverter`` is used.
    c                 S   s
   | d j S )N   )Zcreation_counter)r   r   r   r   <lambda>      zmodel_fields.<locals>.<lambda>)keyc                 s   s   | ]}|d  V  qdS )r   Nr   )rN   xr   r   r   	<genexpr>  s     zmodel_fields.<locals>.<genexpr>c                 3   s   | ]}| kr|V  qd S Nr   rN   r   )field_namesr   r   re     s      c                 3   s   | ]}| kr|V  qd S rf   r   rg   )excluder   r   re     s      N)rA   Z
propertiessortedr   listr[   rX   )
r   onlyri   rY   rZ   propsZsorted_props
field_dictrR   fieldr   )ri   rh   r   model_fields  s    

rp   c                 C   s&   t | ||||}t|  d |f|S )a,  
    Creates and returns a dynamic ``wtforms.Form`` class for a given
    ``db.Model`` class. The form class can be used as it is or serve as a base
    for extended form classes, which can then mix non-model related fields,
    subforms with other model forms, among other possibilities.

    :param model:
        The ``db.Model`` class to generate a form for.
    :param base_class:
        Base form class to extend from. Must be a ``wtforms.Form`` subclass.
    :param only:
        An optional iterable with the property names that should be included in
        the form. Only these properties will have fields.
    :param exclude:
        An optional iterable with the property names that should be excluded
        from the form. All other properties will have fields.
    :param field_args:
        An optional dictionary of field names mapping to keyword arguments
        used to construct each field object.
    :param converter:
        A converter to generate the fields based on the model properties. If
        not set, ``ModelConverter`` is used.
    r   )rp   rP   kind)r   Z
base_classrl   ri   rY   rZ   rn   r   r   r   
model_form  s    rr   )NNNN)*r^   Zwtformsr   r   r   r   r   Zwtforms.compatr   Zwtforms.ext.appengine.fieldsr   r   r	   r   r   r   r!   r"   r#   r$   r*   r+   r,   r-   r.   r1   r2   r3   r4   r5   r6   r8   r:   r;   r<   r=   r>   r@   objectrA   rp   rr   r   r   r   r   <module>   sH   ]	
				   
-