[ADD]Missed out files in previous commit

This commit is contained in:
Sharoon Thomas 2010-12-18 11:09:19 +05:00
parent 4fc1dab3fe
commit 6bef1c5409
8 changed files with 760 additions and 18 deletions

View File

@ -12,5 +12,5 @@ Tryton module to support Nereid
from .routing import *
#from auth import User, Address
from .template import Template
#from static_bank import NereidStaticFolder, NereidStaticFile
from .static_file import NereidStaticFolder, NereidStaticFile
from .party import Address

View File

@ -18,6 +18,7 @@
'xml': [
# 'defaults.xml',
'configuration.xml',
'static_file.xml'
],
'translation': [
],

View File

@ -31,14 +31,18 @@
<data>
<xpath expr="/form/field[@name=&quot;subdivision&quot;]"
position="after">
<newline/>
<label name="email"/>
<field name="email"/>
<label name="phone"/>
<field name="phone"/>
<label name="username"/>
<field name="username"/>
<label name="password"/>
<field name="password"/>
<newline/>
<button string="Create Web Account"
name="create_web_account" type="object"
confirm="Account has been created" colspan="3"/>
<button string="Reset Account"
name="reset_web_account" type="object"
confirm="Account has been reset" colspan="3"/>
</xpath>
</data>
]]>
@ -64,6 +68,11 @@
<field name="active" />
<label name="company"/>
<field name="company"/>
<notebook colspan="4">
<page id="Countries" string="Countries">
<field name="countries"/>
</page>
</notebook>
</form>
]]>
</field>

View File

@ -10,10 +10,17 @@
'''
import random
import string
try:
import hashlib
except ImportError:
hashlib = None
import sha
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Eval
# pylint: disable-msg=E1101
class Address(ModelSQL, ModelView):
"""An address is considered as the equivalent of a user
in a conventional Web application. Hence, the username and
@ -23,13 +30,14 @@ class Address(ModelSQL, ModelView):
#: The email to which all application related emails like
#: registration, password reet etc is managed
email = fields.Many2One('party.contact_mechanism', 'E-Mail')
email = fields.Many2One('party.contact_mechanism', 'E-Mail',
domain=[('party', '=', Eval('party')), ('type', '=', 'email')],
depends=['party'])
#: Similar to email
phone = fields.Many2One('party.contact_mechanism', 'Phone')
#: Username for login
username = fields.Char('Username')
phone = fields.Many2One('party.contact_mechanism', 'Phone',
domain=[('party', '=', Eval('party')), ('type', '=', 'phone')],
depends=['party'])
#: The password is the user password + the salt, which is
#: then hashed together
@ -37,10 +45,82 @@ class Address(ModelSQL, ModelView):
#: The salt which was used to make the hash is separately
#: stored. Needed for
salt = fields.Char('Salt')
salt = fields.Char('Salt', size=8)
def __init__(self):
super(Address, self).__init__()
self._sql_constraints += [
('unique_email', 'UNIQUE(email)',
'email must be unique.'),
]
self._error_messages.update({
'no_email': 'The user does not have an email assigned'
})
self._rpc.update({
'create_web_account': True,
'reset_web_account': True
})
def create_web_account(self, ids):
"""Create a new web account for given address
"""
for address in self.browse(ids):
if not address.email:
self.raise_user_error('no_email')
password = ''.join(
random.sample(string.letters + string.digits, 16))
self.write(address.id, {'password': password})
return True
def reset_web_account(self, ids):
"""Reset the password for the user"""
for address in self.browse(ids):
if not address.email:
self.raise_user_error('no_email')
password = ''.join(
random.sample(string.letters + string.digits, 16))
self.write(address.id, {'password': password})
return True
def authenticate(self, email, password):
"""Assert credentials and if correct return the
browse record of the user
:param email: email of the user
:param password: password of the user
:return: Browse Record or None
"""
contact_mech_obj = self.pool.get('party.contact_mechanism')
contact = contact_mech_obj.search([
('value', '=', email),
('type', '=', 'email')])
if not contact:
return None
ids = self.search([
('email', '=', contact[0])
])
if not ids or len(ids) > 1:
return None
address = self.browse(ids[0])
password += address.salt or ''
if isinstance(password, unicode):
password = password.encode('utf-8')
if hashlib:
password_sha = hashlib.sha1(password).hexdigest()
else:
password_sha = sha.new(password).hexdigest()
if password_sha == address.password:
return address
return None
def _convert_values(self, values):
if 'password' in values:
if 'password' in values and values['password']:
values['salt'] = ''.join(random.sample(
string.ascii_letters + string.digits, 8))
values['password'] += values['salt']

View File

@ -9,10 +9,15 @@
:license: GPLv3, see LICENSE for more details
"""
from ast import literal_eval
from nereid import request
from werkzeug import abort, redirect
from nereid import jsonify, flash, render_template
from nereid.globals import session, request
from trytond.model import ModelView, ModelSQL, fields
from wtforms import Form, TextField, PasswordField, validators
# pylint: disable-msg=E1101
class URLMap(ModelSQL, ModelView):
"""
URL Map
@ -81,6 +86,12 @@ class URLMap(ModelSQL, ModelView):
URLMap()
class LoginForm(Form):
"Default Login Form"
email = TextField('e-mail', [validators.Required(), validators.Email()])
password = PasswordField('Password', [validators.Required()])
class WebSite(ModelSQL, ModelView):
"""
Web Site
@ -116,7 +127,13 @@ class WebSite(ModelSQL, ModelView):
#: records like sale order which require a company to be present
company = fields.Many2One('company.company', 'Company', required=True)
active= fields.Boolean('Active')
active = fields.Boolean('Active')
#: The list of countries this website operates in. Used for generating
#: Countries list in the registration form etc.
countries = fields.Many2Many(
'nereid.website-country.country', 'website', 'country',
'Countries Available')
def default_active(self):
return True
@ -128,6 +145,30 @@ class WebSite(ModelSQL, ModelView):
'Another site with the same name already exists!')
]
def country_list(self):
"""
Return the list of countries in JSON
"""
return jsonify([
{'key': c.id, 'value': c.name} \
for c in request.nereid_website.countries
])
def subdivision_list(self):
"""
Return the list of states for given country
"""
country = int(request.args.get('country', 0))
if country not in [c.id for c in request.nereid_website.countries]:
abort(404)
subdivision_obj = self.pool.get('country.subdivision')
ids = subdivision_obj.search([('country', '=', country)])
subdivisions = subdivision_obj.browse(ids)
return jsonify(
result = [{'key': s.id, 'value': s.name} for s in subdivisions]
)
def get_urls(self, name):
"""
Return complete list of URLs
@ -147,6 +188,155 @@ class WebSite(ModelSQL, ModelView):
return u'Request: %s\nArguments: %s\nEnviron: %s\n' \
% (request, arguments, request.environ)
def home(self):
"""
A sample home method
"""
return u'''Welcome to Nereid
This is the default home page and needs replacing. To build
your own home method, inherit the model nereid.website and implement
the `home` method to replace this function.
'''
def login(self):
"""
Simple login based on the email and password
Required post data see :class:LoginForm
"""
login_form = LoginForm(request.form)
if request.method == 'POST' and login_form.validate():
address_obj = self.pool.get('party.address')
result = address_obj.authenticate(
login_form.email.data, login_form.password.data)
if result is None:
flash("Invalid login credentials")
else:
flash("You are now logged in. Welcome %s" % result.name)
session['user'] = result.id
return redirect(request.args.get('next', '/'))
return render_template('login.jinja', login_form=login_form)
def logout(self):
"Log the user out"
session.pop('user', None)
flash('You have been logged out successfully. Thanks for visiting us')
return redirect(request.args.get('next', '/'))
def registration(self):
register_form = RegistrationForm(request.form)
register_form.country.choices = [
(c.id, c.name) for c in request.nereid_website.countries
]
if request.method == 'POST' and register_form.validate():
address_obj = self.pool.get('party.address')
contact_mech_obj = self.pool.get('party.contact_mechanism')
party_obj = self.pool.get('party.party')
opportunity_obj = self.pool.get('sale.opportunity')
registration_data = register_form.data
# First search if an address with the email already exists
existing = contact_mech_obj.search([
('value', '=', registration_data['email']),
('type', '=', 'email')])
if existing:
flash('A registration already exists with this email. '
'Please contact customer care')
else:
# Create Party
party_id = party_obj.create({
'name': registration_data['company'],
'addresses': [
('create', {
'name': registration_data['name'],
'street': registration_data['street'],
'streetbis': registration_data['streetbis'],
'zip': registration_data['zip'],
'city': registration_data['city'],
'country': registration_data['country'],
'subdivision': registration_data['subdivision'],
})],
})
party = party_obj.browse(party_id)
# Create email as contact mech and assign as email
contact_mech_id = contact_mech_obj.create({
'type': 'email',
'party': party.id,
'email': registration_data['email'],
})
address_obj.write(party.addresses[0].id,
{'email': contact_mech_id})
# Finally create opportunity
opportunity = opportunity_obj.create({
'party': party.id,
'address': party.addresses[0].id,
'company': request.nereid_website.company.id,
'description': 'New Registration',
'comment': registration_data['comments'],
'employee': request.nereid_website.default_employee.id
})
flash('''Your registration has been completed successfully
Our customer care will soon be in touch with you.
Your registration number is: %s''' % opportunity)
return redirect(request.args.get('next', '/'))
return render_template('registration.jinja', form=register_form)
def registration(self):
register_form = RegistrationForm(request.form)
register_form.country.choices = [
(c.id, c.name) for c in request.nereid_website.countries
]
if request.method == 'POST' and register_form.validate():
address_obj = self.pool.get('party.address')
contact_mech_obj = self.pool.get('party.contact_mechanism')
party_obj = self.pool.get('party.party')
registration_data = register_form.data
# First search if an address with the email already exists
existing = contact_mech_obj.search([
('value', '=', registration_data['email']),
('type', '=', 'email')])
if existing:
flash('A registration already exists with this email. '
'Please contact customer care')
else:
# Create Party
party_id = party_obj.create({
'name': registration_data['company'],
'addresses': [
('create', {
'name': registration_data['name'],
'street': registration_data['street'],
'streetbis': registration_data['streetbis'],
'zip': registration_data['zip'],
'city': registration_data['city'],
'country': registration_data['country'],
'subdivision': registration_data['subdivision'],
})],
})
party = party_obj.browse(party_id)
# Create email as contact mech and assign as email
contact_mech_id = contact_mech_obj.create({
'type': 'email',
'party': party.id,
'email': registration_data['email'],
})
address_obj.write(party.addresses[0].id,
{'email': contact_mech_id})
flash('''Your registration has been completed successfully
Our customer care will soon be in touch with you.''')
return redirect(request.args.get('next', '/'))
return render_template('registration.jinja', form=register_form)
WebSite()
@ -199,7 +389,7 @@ class URLRule(ModelSQL, ModelView):
rule = fields.Char('Rule', required=True, select=True,)
endpoint = fields.Char('Endpoint', required=True, select=True,)
active= fields.Boolean('Active')
active = fields.Boolean('Active')
defaults = fields.One2Many('nereid.url_rule_defaults', 'rule', 'Defaults')
methods = fields.Selection(
[
@ -210,7 +400,7 @@ class URLRule(ModelSQL, ModelView):
'Methods', required=True)
only_for_genaration = fields.Boolean('Only for Generation')
redirect_to = fields.Char('Redirect To')
sequence= fields.Integer('Sequence', required=True,)
sequence = fields.Integer('Sequence', required=True,)
url_map = fields.Many2One('nereid.urlmap', 'URL Map')
def __init__(self):
@ -261,3 +451,14 @@ class URLRuleDefaults(ModelSQL, ModelView):
select=True)
URLRuleDefaults()
class WebsiteCountry(ModelSQL):
"Website Country Relations"
_name = 'nereid.website-country.country'
_description = __doc__
website = fields.Many2One('nereid.website', 'Website')
country = fields.Many2One('country.country', 'Country')
WebsiteCountry()

273
static_file.py Normal file
View File

@ -0,0 +1,273 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
nereid.static_file
Static file
:copyright: (c) 2010 by Sharoon Thomas.
:license: BSD, see LICENSE for more details
'''
import os
import base64
from collections import defaultdict
from nereid.helpers import slugify, send_file
from werkzeug import abort
from trytond.model import ModelSQL, ModelView, fields
from trytond.config import CONFIG
from trytond.transaction import Transaction
def get_nereid_path():
"Returns base path for nereid"
cursor = Transaction().cursor
return os.path.join(CONFIG['data_path'], cursor.database_name)
def make_folder_path(folder_name):
"Returns the folder path for given folder"
return os.path.join(get_nereid_path(), folder_name)
def make_file_path(folder_name, file_name):
"Returns the file path for the given folder, file"
return os.path.join(make_folder_path(folder_name), file_name)
def make_file(file_name, file_binary, folder):
"""
Writes file to the FS
:param file_name: Name of the file
:param file_binary: Binary content to save (Base64 encoded)
:param folder: folder name
"""
file_binary = base64.decodestring(file_binary)
file_path = make_file_path(folder, file_name)
with open(file_path, 'wb') as file_writer:
file_writer.write(file_binary)
return file_path
# pylint: disable-msg=E1101
class NereidStaticFolder(ModelSQL, ModelView):
"Static folder for Nereid"
_name = "nereid.static.folder"
_description = __doc__
name = fields.Char('Description', required=True, select=1)
folder_name = fields.Char('Folder Name', required=True, select=1,
on_change_with=['name', 'folder_name'])
comments = fields.Text('Comments')
files = fields.One2Many('nereid.static.file', 'folder', 'Files')
folder_path = fields.Function(fields.Char('Folder Path'), 'get_path')
def __init__(self):
super(NereidStaticFolder, self).__init__()
self._constraints += [
('check_folder_name', 'invalid_folder_name'),
]
self._sql_constraints += [
('unique_folder', 'UNIQUE(folder_name)',
'Folder name needs to be unique')
]
self._error_messages.update({
'invalid_folder_name': """Invalid folder name:
(1) '.' in folder name (OR)
(2) folder name begins with '/'""",
'folder_cannot_change': "Folder name cannot be changed"
})
def get_path(self, ids, name):
"""Return the path of the folder
"""
result = { }
for folder in self.browse(ids):
result[folder.id] = make_folder_path(folder.folder_name)
return result
def on_change_with_folder_name(self, vals):
"""
Fills the name field with a slugified name
"""
if vals.get('name'):
if not vals.get('folder_name'):
vals['folder_name'] = slugify(vals['name'])
return vals['folder_name']
def check_folder_name(self, ids):
'''
Check the validity of folder name
Allowing the use of / or . will be risky as that could
eventually lead to previlege escalation
:param ids: ID of current record.
'''
folder = self.browse(ids[0])
if ('.' in folder.folder_name) or (folder.folder_name.startswith('/')):
return False
return True
def create(self, vals):
"""
Check if the folder exists.
If not, create a new one in data path of tryton.
:param vals: values of the current record
"""
folder_path = make_folder_path(vals.get('folder_name'))
# Create the nereid folder if it doesnt exist
if not os.path.isdir(get_nereid_path()):
os.mkdir(get_nereid_path())
# Create the folder if it doesnt exist
if not os.path.isdir(folder_path):
os.mkdir(folder_path)
return super(NereidStaticFolder, self).create(vals)
def write(self, ids, vals):
"""
Check if the folder_name has been modified.
If yes, raise an error.
:param vals: values of the current record
"""
if vals.get('folder_name'):
self.raise_user_error('folder_cannot_change')
return super(NereidStaticFolder, self).write(ids, vals)
def scan_files_from_fs(self, folder_ids):
"""
Scans File system for images and syncs them
:param folder_ids: ID of the System Folder
"""
file_object = self.pool.get('nereid.static.file')
for folder in self.browse(folder_ids):
existing_filenames = [f.name for f in folder.files]
folder_path = make_folder_path(folder.folder_name)
for content in os.listdir(folder_path):
full_path = os.path.join(folder_path, content)
if (os.path.isfile(full_path)) and \
(content not in existing_filenames):
file_object.create({'name': content, 'folder': folder.id})
return True
NereidStaticFolder()
class NereidStaticFile(ModelSQL, ModelView):
"Static files for Nereid"
_name = "nereid.static.file"
_description = __doc__
name = fields.Char('File Name', select=True, required=True)
file_ = fields.Function(fields.Binary('File'),
'get_field_binary', 'set_content')
folder = fields.Many2One('nereid.static.folder', 'Folder', required=True)
file_path = fields.Function(fields.Char('File Path'), 'get_fields',)
relative_path = fields.Function(fields.Char('Relative Path'), 'get_fields')
def __init__(self):
super(NereidStaticFile, self).__init__()
self._constraints += [
('check_file_name', 'invalid_file_name'),
]
self._sql_constraints += [
('name_folder_uniq', 'UNIQUE(name, folder)',
'The Name of the Static File must be unique in a folder.!'),
]
self._error_messages.update({
'invalid_file_name': """Invalid file name:
(1) '..' in file name (OR)
(2) file name contains '/'""",
})
def set_content(self, ids, name, value):
"""
Creates the file for the function field
"""
for file_ in self.browse(ids):
make_file(file_.name, value, file_.folder.folder_name)
def get_fields(self, ids, names):
'''
Function to compute function fields for sale ids
:param ids: the ids of the sales
:param names: the list of field name to compute
:return: a dictionary with all field names as key and
a dictionary as value with id as key
'''
res = defaultdict(dict)
for name in names:
res[name] = { }
for file_ in self.browse(ids):
file_path = os.path.join(
make_file_path(file_.folder.folder_name, file_.name))
if 'file_path' in names:
res['file_path'][file_.id] = file_path
if 'relative_path' in names:
res['relative_path'][file_.id] = '/'.join([
file_.folder.folder_name,
file_.name])
return res
def get_field_binary(self, ids, name):
"""
This could be part of the above function, but this is an
expensive process which must not affect the rest of the processes
"""
result = {}
for file_ in self.browse(ids):
file_path = os.path.join(
make_file_path(file_.folder.folder_name, file_.name))
with open(file_path, 'rb') as file_handler:
result[file_.id] = base64.encodestring(
file_handler.read()
)
return result
def check_file_name(self, ids):
'''
Check the validity of folder name
Allowing the use of / or . will be risky as that could
eventually lead to previlege escalation
:param ids: ID of current record.
'''
file_ = self.browse(ids[0])
if ('..' in file_.name) or ('/' in file_.name):
return False
return True
def send_static_file(self, folder, name):
'''
Send the static file
'''
#TODO: Separate this search and find into separate cached method
ids = self.search([
('folder.folder_name', '=', folder),
('name', '=', name)
])
if not ids:
abort(404)
file_ = self.browse(ids[0])
return send_file(file_.file_path)
NereidStaticFile()

149
static_file.xml Normal file
View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of Tryton & Nereid. The COPYRIGHT file at the
top level of this repository contains the full copyright notices
and license terms.
-->
<tryton>
<data>
<!-- Static Bank -->
<record id="nereid_static_folder_form" model="ir.ui.view">
<field name="model">nereid.static.folder</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Static Folder">
<label name="name" />
<field name="name" />
<label name="folder_name" />
<field name="folder_name" />
<newline />
<field name="files" colspan="4" >
<form string="File">
<label name="name" />
<field name="name" />
<label name="file_" />
<field name="file_" />
<label name="folder" />
<field name="folder" />
<label name="file_path" />
<field name="file_path" />
</form>
<tree string="Files">
<field name="name" />
<field name="folder" />
<field name="file_path" />
</tree>
</field>
<button name="scan_files_from_fs"
string="Create files from folder"
type="object"
colspan="2" />
<separator colspan="4" string="Comments"
id="sepr_comments"/>
<field name="comments" colspan="4" />
</form>
]]>
</field>
</record>
<record id="nereid_static_folder_tree" model="ir.ui.view">
<field name="model">nereid.static.folder</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree>
<field name="name" />
<field name="folder_name" />
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="action_nereid_static_folder_view">
<field name="name">Nereid Static Folders</field>
<field name="res_model">nereid.static.folder</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view" id="act_nereid_static_folder_view1">
<field name="sequence" eval="10" />
<field name="view" ref="nereid_static_folder_tree" />
<field name="act_window" ref="action_nereid_static_folder_view" />
</record>
<record model="ir.action.act_window.view" id="act_nereid_static_folder_view2">
<field name="sequence" eval="20" />
<field name="view" ref="nereid_static_folder_form" />
<field name="act_window" ref="action_nereid_static_folder_view" />
</record>
<menuitem id="menu_nereid_static"
parent="menu_nereid"
name="Static"
groups="nereid.group_nereid_admin" />
<menuitem name="Static Folders" sequence="20"
id="menu_nereid_config_static_folder"
action="action_nereid_static_folder_view"
parent="menu_nereid_static"
groups="nereid.group_nereid_admin" />
<record id="nereid_static_file_form" model="ir.ui.view">
<field name="model">nereid.static.file</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form>
<label name="file_" />
<field name="file_" />
<label name="name" />
<field name="name" />
<label name="folder" />
<field name="folder" />
<separator colspan="4" id="paths" string="Paths"/>
<label name="relative_path" />
<field name="relative_path" />
<label name="file_path" />
<field name="file_path" />
<separator string="Preview"
colspan="4" id="sepr_preview"/>
<field name="file_" widget="image" colspan="4"/>
</form>
]]>
</field>
</record>
<record id="nereid_static_file_tree" model="ir.ui.view">
<field name="model">nereid.static.file</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree>
<field name="name" />
<field name="folder" />
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="action_nereid_static_file_view">
<field name="name">Nereid Static Files</field>
<field name="res_model">nereid.static.file</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view" id="act_nereid_static_file_view1">
<field name="sequence" eval="10" />
<field name="view" ref="nereid_static_file_tree" />
<field name="act_window" ref="action_nereid_static_file_view" />
</record>
<record model="ir.action.act_window.view" id="act_nereid_static_file_view2">
<field name="sequence" eval="20" />
<field name="view" ref="nereid_static_file_form" />
<field name="act_window" ref="action_nereid_static_file_view" />
</record>
<menuitem id="menu_nereid_config_static_file"
parent="menu_nereid_static"
name="Static Files"
action="action_nereid_static_file_view" />
</data>
</tryton>

View File

@ -9,6 +9,8 @@
:license: GPLv3, see LICENSE for more details
"""
from trytond.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction
class Template(ModelSQL, ModelView):
"""
@ -52,9 +54,9 @@ class Template(ModelSQL, ModelView):
If not found it returns None
"""
lang_obj = self.pool.get('res.lang')
lang_obj = self.pool.get('ir.lang')
lang_id, = lang_obj.search(
[('code', '=', context.get('language', 'en_US'))]
[('code', '=', Transaction().context.get('language', 'en_US'))]
)
template_ids = self.search(
[('name', '=', name), ('language', '=', lang_id)]
@ -66,3 +68,30 @@ class Template(ModelSQL, ModelView):
Template()
class ContextProcessors(ModelSQL, ModelView):
"Temlate Context Processor Registry"
_name = 'nereid.template.context_processor'
_description = __doc__
_rec_name = 'function'
method = fields.Char('Method', required=True,
help="Context processor method in <model>.<method>")
model = fields.Char('Model',
help="This will restrict the loading when URLs with"
" the model are called")
def get_processors(self):
"""Return the list of processors. Separate function
since its important to have caching on this
"""
result = { }
ids = self.search([])
for ctx_proc in self.browse(ids):
model, method = ctx_proc.method.rsplit('.', 1)
ctx_proc_as_func = getattr(self.pool.get(model), method)
result.setdefault(ctx_proc.model or None, []).append(
ctx_proc_as_func)
return result
ContextProcessors()