Source code for webnotes.model.doctype

"""
 DocType module
 ==============
 
 This module has the DocType class that represents a "DocType" as metadata.
 This is usually called by the form builder or report builder.

 Key functions:
	* manage cache - read / write
	* merge client-side scripts
	* update properties from the modules .txt files

 Cache management:
	* Cache is stored in __DocTypeCache
"""

import webnotes
import webnotes.model
import webnotes.model.doclist
import webnotes.model.doc

from webnotes.utils import cstr

class _DocType:
	"""
	   The _DocType object is created internally using the module's `get` method.
	"""
	def __init__(self, name):
		self.name = name
	
	# is cache modified ?
	# =================================================================
	
	def is_modified(self):
		"""
			Returns true if modified
		"""		
		try:
			# doctype modified
			modified = webnotes.conn.sql("select modified from tabDocType where name=%s", self.name)[0][0]
	
			# cache modified
			cache_modified = webnotes.conn.sql("SELECT modified from `__DocTypeCache` where name='%s'" % self.name)[0][0]
			
		except IndexError, e:
			return 1

		return cache_modified != modified

	# write to cache
	# =================================================================

	def _update_cache(self, doclist):
		import zlib

		if webnotes.conn.sql("SELECT name FROM __DocTypeCache WHERE name=%s", self.name):
			webnotes.conn.sql("UPDATE `__DocTypeCache` SET `modified`=%s, `content`=%s WHERE name=%s", (doclist[0].modified, zlib.compress(str([d.fields for d in doclist]),2), self.name))
		else:
			webnotes.conn.sql("INSERT INTO `__DocTypeCache` (`name`, `modified`, `content`) VALUES (%s, %s, %s)" , (self.name, doclist[0].modified, zlib.compress(str([d.fields for d in doclist]))))

	# read from cache
	# =================================================================

	def _load_from_cache(self):
		import zlib
	
		doclist = eval(zlib.decompress(webnotes.conn.sql("SELECT content from `__DocTypeCache` where name=%s", self.name)[0][0]))
		return [webnotes.model.doc.Document(fielddata = d) for d in doclist]


	# load options for "link:" type 'Select' fields
	# =================================================================
			
	def _load_select_options(self, doclist):
		for d in doclist:
			if d.doctype=='DocField' and d.fieldtype=='Select' and d.options and d.options[:5].lower()=='link:':
				op = d.options.split('\n')
				if len(op)>1 and op[1][:4].lower() == 'sql:':
					ol = webnotes.conn.sql(op[1][4:].replace('__user', webnotes.session['user']))	
				else:
					t = op[0][5:].strip()
					op = op[1:]
					op = [oc.replace('__user', webnotes.session['user']) for oc in op]
					
					try:
						# select options will always come from the user db
						ol = webnotes.conn.sql("select name from `tab%s` where %s docstatus!=2 order by name asc" % (t, op and (' AND '.join(op) + ' AND ') or ''))
					except:
						ol = []
				ol = [''] + [o[0] or '' for o in ol]
				d.options = '\n'.join(ol)

	# clear un-necessary code from going to the client side
	# =================================================================

	def _clear_code(self, doclist):
		if self.name != 'DocType':
			if doclist[0].server_code: doclist[0].server_code = None
			if doclist[0].server_code_core: doclist[0].server_code_core = None
			if doclist[0].client_script: doclist[0].client_script = None
			if doclist[0].client_script_core: doclist[0].client_script_core = None
			doclist[0].server_code_compiled = None

	# build a list of all the records required for the DocType
	# =================================================================
	
	def _get_last_update(self, dt):
		"""
			Returns last update timestamp
		"""
		try:
			last_update = webnotes.conn.sql("select _last_update from tabDocType where name=%s", dt)[0][0]
		except Exception, e:
			if e.args[0]==1054: 
				self._setup_last_update(dt)
				last_update = None
		
		return last_update

	def _setup_last_update(self, doctype):
		"""
			Adds _last_update column to tabDocType

		"""
		webnotes.conn.commit()
		webnotes.conn.sql("alter table `tabDocType` add column _last_update varchar(32)")
		webnotes.conn.begin()
		
	def _get_fields(self, file_name):
		"""
			Returns a dictionary of DocFields by fieldname or label
		"""
		try:
			doclist = open(file_name, 'r').read()
		except:
			return
		doclist = eval(doclist)
		fields = {}
		for d in doclist:
			if d['doctype']=='DocField':
				if d['fieldname'] or d['label']:
					fields[d['fieldname'] or d['label']] = d
		return fields
		
	def _update_field_properties(self, doclist):
		"""
			Updates properties like description, depends on from the database based on the timestamp
			of the .txt file. Adds a column _last_updated if not exists in the database and uses
			it to update the file..
			
			This feature is built because description is changed / updated quite often and is tedious to
			write a patch every time. Can be extended to cover more updates
		"""
		
		update_fields = ('description', 'depends_on')

		from webnotes.modules import get_file_timestamp, get_item_file

		doc = doclist[0] # main doc
		file_name = get_item_file(doc.module, 'DocType', doc.name)
		time_stamp = get_file_timestamp(file_name)
		last_update = self._get_last_update(doc.name)
		
		# this is confusing because we are updating the fields of fields
		
		if last_update != time_stamp:

			# there are updates!
			fields = self._get_fields(file_name)
			if fields:
				for d in doclist:
				
					# for each field in teh outgoing doclist
					if d.doctype=='DocField':
						key = d.fieldname or d.label
					
						# if it has a fieldname or label
						if key and key in fields:
						
							# update the values
							for field_to_update in update_fields:
								new_value = fields[key][field_to_update]
							
								# in doclist
								d.fields[field_to_update] = new_value
							
								# in database
								webnotes.conn.sql("update tabDocField set `%s` = %s where parent=%s and `%s`=%s" % \
									(field_to_update, '%s', '%s', (d.fieldname and 'fieldname' or 'label'), '%s'), \
									(new_value, doc.name, key))
					
				webnotes.conn.sql("update tabDocType set _last_update=%s where name=%s", (time_stamp, doc.name))
	
	def _override_field_properties(self, doclist):
		"""
			Override field properties that are updated by "Property Setter"
			The term "property" is used to define a fieldname of a field to avoid confusing
			terminology
		"""
		# load field properties and add them to a dictionary
		property_dict = {}
		dt = doclist[0].name
		try:
			for f in webnotes.conn.sql("select doc_name, property, property_type, value from `tabProperty Setter` where doc_type=%s", dt, as_dict=1):
				if not f['doc_name'] in property_dict:
					property_dict[f['doc_name']] = []
				property_dict[f['doc_name']].append(f)
		except Exception, e:
			if e.args[0]==1146:
				# no override table
				pass
			else: 
				raise e

		# loop over fields and override property
		for d in doclist:
			if d.doctype=='DocField' and d.name in property_dict:
				for p in property_dict[d.name]:
					if p['property_type']=='Check':
						d.fields[p['property']] = int(p['value'])
					else:
						d.fields[p['property']] = p['value']
					
		# override properties in the main doctype
		if dt in property_dict:
			for p in property_dict[self.name]:
				doclist[0].fields[p['property']] = p['value']
				

	def make_doclist(self):
		"""
		      returns the :term:`doclist` for consumption by the client
		      
		      * it cleans up the server code
		      * executes all `$import` tags in client code
		      * replaces `link:` in the `Select` fields
		      * loads all related `Search Criteria`
		      * updates the cache
		"""
		from webnotes.modules import compress
		
		tablefields = webnotes.model.meta.get_table_fields(self.name)

		if self.is_modified():
			# yes
			doclist = webnotes.model.doc.get('DocType', self.name, 1)
			
			self._update_field_properties(doclist)
			self._override_field_properties(doclist)
			
			# table doctypes
			for t in tablefields: 
				table_doclist = webnotes.model.doc.get('DocType', t[0], 1)
				
				self._override_field_properties(table_doclist)
				doclist += table_doclist

			# don't save compiled server code

		else:
			doclist = self._load_from_cache()
			
		doclist[0].fields['__client_script'] = compress.get_doctype_js(self.name)
		self._load_select_options(doclist)
		self._clear_code(doclist)

		return doclist

[docs]def clear_cache(): webnotes.conn.sql("delete from __DocTypeCache") # Load "DocType" - called by form builder, report buider and from code.py (when there is no cache) #=================================================================================================
[docs]def get(dt): """ Load "DocType" - called by form builder, report buider and from code.py (when there is no cache) """ doclist = _DocType(dt).make_doclist() return doclist