# Copyright 2008 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Custom fields and widgets for Gerrit. This requires Django 0.97.pre. """ ### Imports ### import string import logging import django.forms.widgets import django.forms.fields from django import forms from django.utils import encoding from django.utils import safestring from django.forms import util from django.utils import html from django.utils import simplejson from google.appengine.ext import db import models def person_to_dict(v): entry = {} if isinstance(v, models.Account): entry["type"] = "user" entry["key"] = "user/" + v.email entry["email"] = v.email entry["real_name"] = v.real_name entry["sort_key"] = "2/" + unicode(v.user) elif isinstance(v, models.AccountGroup): entry["type"] = "group" entry["key"] = "group/" + str(v.key()) entry["name"] = v.name entry["sort_key"] = "1/" + unicode(v.name) else: raise AssertionError("bad value: " + str(v)) return entry def people_to_dicts(value): data = [] for v in value: if isinstance(v, list): data.extend(people_to_dicts(v)) elif v: data.append(person_to_dict(v)) data.sort(lambda a,b: cmp(a["sort_key"], b["sort_key"])) return data ### User/Group Field ### class UserGroupWidget(django.forms.widgets.Widget): """The widget that is used with UserGroupField.""" def __init__(self, allow_users=True, allow_groups=True, attrs=None): self.attrs = {'cols': '40', 'rows': '10'} self.allow_users = allow_users self.allow_groups = allow_groups if attrs: self.attrs.update(attrs) def render(self, name, value, attrs=None): if value is None: value = [] return safestring.mark_safe( u"""
""" % { "name":name, "initial":self._render_initial_js(value), "allow_users": ("true" if self.allow_users else "false"), "allow_groups": ("true" if self.allow_groups else "false"), }) def _render_initial_js(self, value): data = people_to_dicts(value) return "[%s]" % ','.join(map(simplejson.dumps, data)) def value_from_datadict(self, data, files, name): return data.getlist(name + "_keys") class UserGroupField(django.forms.fields.Field): """A Field that picks a list of users and groups.""" def __init__(self, *args, **kwargs): self.allow_users = kwargs.pop("allow_users", True) self.allow_groups = kwargs.pop("allow_groups", True) self.widget = UserGroupWidget(self.allow_users, self.allow_groups) super(UserGroupField, self).__init__(*args, **kwargs) def clean(self, data, initial=None): def get_correct_model(key): (type,id) = key.split("/", 1) if id: try: if type == "user": return models.Account.get_account_for_email(id) elif type == "group": return models.AccountGroup.get(id) except db.BadKeyError, v: pass raise forms.ValidationError("invalid key") keys = data result = [get_correct_model(key) for key in keys] super(UserGroupField, self).clean(initial or result) return result @classmethod def get_users(cls, cleaned): """Returns the users, given the cleaned data from the form. e.g. model_obj.usrs = fields.UserGroupField.get_users(form.cleaned_data['field']) """ return [x.user for x in cleaned if isinstance(x, models.Account)] @classmethod def get_groups(cls, cleaned): """Returns the groups, given the cleaned data from the form. e.g. groups = fields.UserGroupField.get_users(form.cleaned_data['field']) """ return [x for x in cleaned if isinstance(x, models.AccountGroup)] @classmethod def get_group_keys(cls, cleaned): """Returns keys for the groups, given the cleaned data from the form. e.g. groups = fields.UserGroupField.get_users(form.cleaned_data['field']) """ return [x.key() for x in cleaned if isinstance(x, models.AccountGroup)] @classmethod def get_user_and_group_keys(cls, cleaned): """Returns the users and the groups for the cleaned data from the form. e.g. (model_obj.users,model_obj.groups ) = fields.UserGroupField.get_user_and_group_keys( form.cleaned_data['field']) """ return (UserGroupField.get_users(cleaned), UserGroupField.get_group_keys(cleaned)) @classmethod def field_value_for_keys(cls, users=[], groups=[]): """Return the value suitable for this field from a list keys. e.g. form_initial_values['field'] = fields.UserGroupField.field_value_for_keys( users, group_keys) """ return ([models.AccountGroup.get(k) for k in groups] + [models.Account.get_account_for_user(u) for u in users]) ### Approvers Field ### class ApproversWidget(django.forms.widgets.Widget): """The widget for ApproversField""" def __init__(self, allow_users=True, allow_groups=True, attrs=None, approvers=None, verifiers=None): self.attrs = {'cols': '40', 'rows': '10'} if attrs: self.attrs.update(attrs) self.approvers = approvers or UserGroupWidget(); self.verifiers = verifiers or UserGroupWidget(); def render(self, name, value, attrs=None): if value is None: value = [] styles = self.attrs.get("styles", {}) return safestring.mark_safe( u""" """ % { "name": name, "initial": self._render_initial_js(name, value), "styles": simplejson.dumps(styles), }) def _render_initial_js(self, name, value): data = [] index = 0 for v in value: # BEGIN DEBUGGING key = "initial_%d" % index files = v["files"] bad_files = v.get("bad_files", []) approvers = v["approvers"] verifiers = v["verifiers"] # END DEBUGGING entry = {} entry["key"] = "initial_%d" % index entry["files"] = encoding.force_unicode("\n".join(files)) entry["bad_files"] = map(encoding.force_unicode, bad_files) entry["approvers"] = people_to_dicts(approvers) entry["verifiers"] = people_to_dicts(verifiers) data.append(entry) index = index + 1 rows = [] for entry in data: rows.append(simplejson.dumps(entry)) return "[%s]" % ','.join(rows) def value_from_datadict(self, data, files, name): result = [] keys = data.getlist(name + "_keys") for key in keys: field_key = "%s_%s" % (name, key) files = filter(string.strip, data.get(field_key + "_files").splitlines()) bad_files = [] for f in files: if not models.ApprovalRight.validate_file(f): err = True bad_files.append(f) approvers = self.approvers.value_from_datadict(data, files, field_key + "_approvers") verifiers = self.verifiers.value_from_datadict(data, files, field_key + "_verifiers") result.append({ "key": key, "files": files, "bad_files": bad_files, "approvers": approvers, "verifiers": verifiers, }) return result class ApproversField(django.forms.fields.Field): """A Field to pick which users/groups can edit which field""" approvers = UserGroupField(); verifiers = UserGroupField(); widget = ApproversWidget(approvers=approvers.widget, verifiers=verifiers.widget, attrs={"styles": { "approval": "approval" }}) def __init__(self, *args, **kwargs): super(ApproversField, self).__init__(*args, **kwargs) def clean(self, data, initial=None): result = [] err = False for d in data: files = d["files"] if len(d["bad_files"]) > 0: err = True approvers = self.approvers.clean(d["approvers"]) verifiers = self.verifiers.clean(d["verifiers"]) result.append({"files": files, "approvers": approvers, "verifiers": verifiers}) if False: for r in result: logging.info("clean: files=" + str(r["files"])) logging.info(" approvers=" + str(r["approvers"])) logging.info(" verifiers=" + str(r["verifiers"])) super(ApproversField, self).clean(initial or result) if err: raise forms.ValidationError("invalid files") return result ### Project field ### class ProjectSelectWidget(django.forms.widgets.Widget): """A widget that lets a user pick a set of projects.""" def __init__(self, attrs=None): super(ProjectSelectWidget, self).__init__(attrs) if attrs: self.attrs.update(attrs) def render(self, name, value, attrs=None): if value is None: value = [] project_list = [{'name': p.name, 'key': str(p.key())} for p in models.Project.get_all_projects()] return safestring.mark_safe( u""" """ % { "name": name, "project_list": self._render_js_list(project_list), "initial": self._render_js_list([str(v) for v in value]), }) def _render_js_list(self, value): return "[%s]" % ','.join(map(simplejson.dumps, value)) def value_from_datadict(self, data, files, name): return set([v.strip() for v in data.getlist(name) if len(v.strip()) > 0]) class ProjectSelectField(django.forms.fields.Field): """A Field that lets a user pick a set of projects.""" def __init__(self, *args, **kwargs): self.widget = ProjectSelectWidget() super(ProjectSelectField, self).__init__(*args, **kwargs) def clean(self, data, initial=None): objects = models.Project.get(data) result = [o.key() for o in objects if o] super(ProjectSelectField, self).clean(initial or result) return result