diff options
| -rw-r--r-- | archaeological_context_records/models.py | 33 | ||||
| -rw-r--r-- | archaeological_files/forms.py | 33 | ||||
| -rw-r--r-- | archaeological_files/models.py | 36 | ||||
| -rw-r--r-- | archaeological_finds/models.py | 40 | ||||
| -rw-r--r-- | archaeological_operations/models.py | 33 | ||||
| -rw-r--r-- | ishtar_common/models.py | 30 | ||||
| -rw-r--r-- | ishtar_common/static/media/style.css | 75 | ||||
| -rw-r--r-- | ishtar_common/templates/base.html | 2 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/dashboards/dashboard_main.html | 6 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html | 110 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/dashboards/dashboard_main_detail_users.html | 8 | ||||
| -rw-r--r-- | ishtar_common/views.py | 55 | ||||
| -rw-r--r-- | ishtar_common/widgets.py | 658 | 
13 files changed, 645 insertions, 474 deletions
diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 4a288b5dc..2654788f7 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -198,17 +198,25 @@ class ContextRecord(BaseHistorizedItem, OwnPerms, ShortMenuItem):          return self.operation.get_town_label()      @classmethod -    def get_years(cls): -        years = set() -        for res in list(cls.objects.values('operation__start_date')): -            if res['operation__start_date']: -                yr = res['operation__start_date'].year -                years.add(yr) -        return list(years) +    def get_periods(cls, slice='year', fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        if slice == 'year': +            years = set() +            for res in list(q.values('operation__start_date')): +                if res['operation__start_date']: +                    yr = res['operation__start_date'].year +                    years.add(yr) +            return list(years) +        return []      @classmethod -    def get_by_year(cls, year): -        return cls.objects.filter(operation__start_date__year=year) +    def get_by_year(cls, year, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(operation__start_date__year=year)      @classmethod      def get_operations(cls): @@ -220,8 +228,11 @@ class ContextRecord(BaseHistorizedItem, OwnPerms, ShortMenuItem):          return cls.objects.filter(operation__pk=operation_id)      @classmethod -    def get_total_number(cls): -        return cls.objects.filter(operation__start_date__isnull=False).count() +    def get_total_number(cls, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.count()      def find_docs_q(self):          from archaeological_finds.models import FindSource diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py index 4ff483a75..e3b480a57 100644 --- a/archaeological_files/forms.py +++ b/archaeological_files/forms.py @@ -122,6 +122,39 @@ class FileFormSelection(forms.Form):              raise forms.ValidationError(_(u"You should select a file."))          return cleaned_data +SLICING = (('year',_(u"years")), ("month",_(u"months"))) + +class DashboardForm(forms.Form): +    slicing = forms.ChoiceField(label=_("Slicing"), choices=SLICING, +                              required=False) +    file_type = forms.ChoiceField(label=_("File type"), choices=[], +                                  required=False) +    saisine_type = forms.ChoiceField(label=_("Saisine type"), choices=[], +                                  required=False) +    after = forms.DateField(label=_(u"Creation date after"), +                            widget=widgets.JQueryDate, required=False) +    before = forms.DateField(label=_(u"Creation date before"), +                            widget=widgets.JQueryDate, required=False) + +    def __init__(self, *args, **kwargs): +        super(DashboardForm, self).__init__(*args, **kwargs) +        self.fields['saisine_type'].choices = models.SaisineType.get_types() +        self.fields['file_type'].choices = models.FileType.get_types() + +    def get_filter(self): +        if not hasattr(self, 'cleaned_data') or not self.cleaned_data: +            return {} +        fltr = {} +        if self.cleaned_data.get('saisine_type'): +            fltr['saisine_type_id'] = self.cleaned_data['saisine_type'] +        if self.cleaned_data.get('file_type'): +            fltr['file_type_id'] = self.cleaned_data['file_type'] +        if self.cleaned_data.get('after'): +            fltr['creation_date__gte'] = self.cleaned_data['after'] +        if self.cleaned_data.get('before'): +            fltr['creation_date__lte'] = self.cleaned_data['before'] +        return fltr +  class FileFormGeneral(forms.Form):      form_label = _("General")      associated_models = {'in_charge':Person, diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 6d5390737..a1e8912f4 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -230,17 +230,39 @@ class File(BaseHistorizedItem, OwnPerms, ValueGetter, ShortMenuItem):          return sorted(owns.all(), key=lambda x:x.cached_label)      @classmethod -    def get_years(cls): -        return [res['year'] for res in list(cls.objects.values('year').annotate( -                                             Count("id")).order_by())] +    def get_periods(cls, slice='year', fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        if slice == 'year': +            return [res['year'] for res in list(q.values('year' +                                ).annotate(Count("id")).order_by())] +        elif slice == 'month': +            return [(res['creation_date'].year, res['creation_date'].month) +                               for res in list(q.values('creation_date' +                                ).annotate(Count("id")).order_by())] +        return []      @classmethod -    def get_by_year(cls, year): -        return cls.objects.filter(year=year) +    def get_by_year(cls, year, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(year=year)      @classmethod -    def get_total_number(cls): -        return cls.objects.count() +    def get_by_month(cls, year, month, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(creation_date__year=year, creation_date__month=month) + +    @classmethod +    def get_total_number(cls, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.count()      def get_values(self, prefix=''):          values = super(File, self).get_values(prefix=prefix) diff --git a/archaeological_finds/models.py b/archaeological_finds/models.py index cdc87ea95..2f4c59668 100644 --- a/archaeological_finds/models.py +++ b/archaeological_finds/models.py @@ -233,22 +233,29 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem):          return bf.context_record.operation.get_town_label()      @classmethod -    def get_years(cls): -        years = set() -        finds = cls.objects.filter(downstream_treatment__isnull=True) -        for find in finds: -            bi = find.base_finds.all() -            if not bi: -                continue -            bi = bi[0] -            if bi.context_record.operation.start_date: -                yr = bi.context_record.operation.start_date.year -                years.add(yr) +    def get_periods(cls, slice='year', fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        if slice == 'year': +            years = set() +            finds = q.filter(downstream_treatment__isnull=True) +            for find in finds: +                bi = find.base_finds.all() +                if not bi: +                    continue +                bi = bi[0] +                if bi.context_record.operation.start_date: +                    yr = bi.context_record.operation.start_date.year +                    years.add(yr)          return list(years)      @classmethod -    def get_by_year(cls, year): -        return cls.objects.filter(downstream_treatment__isnull=True, +    def get_by_year(cls, year, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(downstream_treatment__isnull=True,                  base_finds__context_record__operation__start_date__year=year)      @classmethod @@ -270,8 +277,11 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem):                  base_finds__context_record__operation__pk=operation_id)      @classmethod -    def get_total_number(cls): -        return cls.objects.filter(downstream_treatment__isnull=True).count() +    def get_total_number(cls, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(downstream_treatment__isnull=True).count()      def duplicate(self, user):          dct = dict([(attr, getattr(self, attr)) for attr in ('order', 'label', diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index d865c1ff9..c7a8c94ac 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -335,21 +335,32 @@ class Operation(BaseHistorizedItem, OwnPerms, ValueGetter, ShortMenuItem):          return (max_val + 1) if max_val else 1      @classmethod -    def get_years(cls): -        max_year = datetime.date.today().year + 1 -        return [res['year'] for res in list( -                cls.objects.exclude( year__isnull=True -                          ).exclude(year__gt=max_year -                          ).values('year' -                          ).annotate(Count("id")).order_by())] +    def get_periods(cls, slice='year', fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        if slice == 'year': +            max_year = datetime.date.today().year + 1 +            return [res['year'] for res in list( +                            q.exclude( year__isnull=True +                              ).exclude(year__gt=max_year +                              ).values('year' +                              ).annotate(Count("id")).order_by())] +        return []      @classmethod -    def get_by_year(cls, year): -        return cls.objects.filter(year=year) +    def get_by_year(cls, year, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.filter(year=year)      @classmethod -    def get_total_number(cls): -        return cls.objects.count() +    def get_total_number(cls, fltr={}): +        q = cls.objects +        if fltr: +            q = q.filter(**fltr) +        return q.count()      year_index_lbl = _(u"Operation code")      @property diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 5c6e208d7..39bb4aeb0 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -561,9 +561,9 @@ class UserDashboard:                            .order_by('person__person_types')  class Dashboard: -    def __init__(self, model): +    def __init__(self, model, slice='year', fltr={}):          self.model = model -        self.total_number = model.get_total_number() +        self.total_number = model.get_total_number(fltr)          history_model = self.model.history.model          # last edited - created          self.recents, self.lasts = [], [] @@ -586,14 +586,26 @@ class Dashboard:                  obj.history_date = idx['hd']                  last_lst.append(obj)          # years -        self.years = model.get_years() -        self.years.sort() -        if not self.total_number or not self.years: +        self.periods = model.get_periods(slice=slice, fltr=fltr) +        self.periods = list(set(self.periods)) +        self.periods.sort() +        if not self.total_number or not self.periods:              return -        self.values = [('year', _(u"Year"), reversed(self.years))]          # numbers -        self.numbers = [model.get_by_year(year).count() for year in self.years] -        self.values += [('number', _(u"Number"), reversed(self.numbers))] +        if slice == 'year': +            self.values = [('year', _(u"Year"), reversed(self.periods))] +            self.numbers = [model.get_by_year(year, fltr=fltr).count() +                            for year in self.periods] +            self.values += [('number', _(u"Number"), reversed(self.numbers))] +        if slice == 'month': +            self.numbers = [model.get_by_month(*period, fltr=fltr).count() +                            for period in self.periods] +            periods = reversed(self.periods) +            self.periods = ["%d-%s-01" % (period[0], ('0'+str(period[1])) +                    if len(str(period[1])) == 1 else period[1]) +                            for period in periods] +            self.values = [('month', _(u"Month"), self.periods)] +            self.values += [('number', _(u"Number"), reversed(self.numbers))]          # calculate          self.average = self.get_average()          self.variance = self.get_variance() @@ -647,7 +659,7 @@ class Dashboard:      def get_mode(self, vals={}):          if not vals: -            vals = dict(zip(self.years, self.numbers)) +            vals = dict(zip(self.periods, self.numbers))          mx = max(vals.values())          for v in vals:              if vals[v] == mx: diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css index 32e957b74..341308926 100644 --- a/ishtar_common/static/media/style.css +++ b/ishtar_common/static/media/style.css @@ -426,9 +426,9 @@ div.form {      margin-left:auto;      margin-right:auto;      margin-bottom:40px; -    padding:1em; +    padding:20px;      display:block; -    width:740px; +    width:720px;      text-align:center;  } @@ -543,6 +543,25 @@ table.confirm tr.spacer td:last-child{      background-image:none;  } +.ui-widget-header { +    background:none; +} + +.ui-tabs .ui-tabs-nav li a{ +    color: #D14; +} + +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { +    background:none; +    background-color: #F1F2F6; +    border: 1px solid #CCC; +    border-radius-top: 4px; +} + +.ui-tabs .ui-tabs-nav li.ui-tabs-active a{ +    background-color:#fff; +} +  .sheet{      width:760px;      position:fixed; @@ -573,25 +592,6 @@ table.confirm tr.spacer td:last-child{      right:135px;  } -.ui-widget-header { -    background:none; -} - -.ui-tabs .ui-tabs-nav li a{ -    color: #D14; -} - -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { -    background:none; -    background-color: #F1F2F6; -    border: 1px solid #CCC; -    border-radius-top: 4px; -} - -.ui-tabs .ui-tabs-nav li.ui-tabs-active a{ -    background-color:#fff; -} -  #dashboard{      padding-top: 20px;  } @@ -638,13 +638,22 @@ table.confirm tr.spacer td:last-child{      padding:0 10px;  } +.dashboard div.form{ +    padding:20px 10px; +    width:740px; +} + +.dashboard select { +    max-width: 400px; +} +  #window .table, .dashboard .table{      padding:10px;      width:730px;      overflow:auto;  } -#window table, .dashboard table{ +#window table, .dashboard table.resume{      font-size:0.9em;      margin:10px 0;      border-collapse:collapse; @@ -661,7 +670,7 @@ table.confirm tr.spacer td:last-child{  } -#window table th, .dashboard table th{ +#window table th, .dashboard table.resume th{      background-color:#922;      border:1px solid #f1f2f6;      color:#FFF; @@ -677,33 +686,33 @@ table.confirm tr.spacer td:last-child{      margin-right:auto;  } -.dashboard table th{ +.dashboard table.resume th{      text-align:center;      padding:0.5em;  } -#window table th.sub, .dashboard table th.sub, .dashboard table td.sub{ +#window table th.sub, .dashboard table.resume th.sub, .dashboard table.resume td.sub{      background-color:#994242;      border:1px solid #f1f2f6;      color:#FFF;      padding:0 1em;  } -#window table th.sub, .dashboard table th.sub{ +#window table.resume th.sub, .dashboard table.resume th.sub{      text-align:left;  } -#window table td, .dashboard table td{ +#window table td, .dashboard table.resume td{      text-align:right;      padding:0 1em;      border:1px solid #f1f2f6;  } -#window table td.string, .dashboard table td.string{ +#window table td.string, .dashboard table.resume td.string{      text-align:left;  } -#window table td.ref, .dashboard table td.ref{ +#window table td.ref, .dashboard table.resume td.ref{      text-align:left;      white-space:nowrap;      font-family:monospace; @@ -820,6 +829,10 @@ a.remove{      margin:0 6px;  } +.dashboard .jqplot-target table{ +    width:auto; +} +  .dashboard table.jqplot-table-legend{      width:default;      border-collapse:default; @@ -835,11 +848,11 @@ a.remove{      padding:0;  } -.dashboard table{ +.dashboard table.resume{      width:100%;      border-collapse:yes;  } -.dashboard table th, .dashboard table td{ +.dashboard table.resume th, .dashboard table.resume td{      border:1px solid;  } diff --git a/ishtar_common/templates/base.html b/ishtar_common/templates/base.html index bfacee069..1ebebd815 100644 --- a/ishtar_common/templates/base.html +++ b/ishtar_common/templates/base.html @@ -2,7 +2,7 @@  {% load url from future%}  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{{LANGUAGE_CODE}}" lang="{{LANGUAGE_CODE}}">  <head>      <link rel="shortcut icon" href="{{STATIC_URL}}/media/images/favicon.png"/> diff --git a/ishtar_common/templates/ishtar/dashboards/dashboard_main.html b/ishtar_common/templates/ishtar/dashboards/dashboard_main.html index 06b671343..8822875b5 100644 --- a/ishtar_common/templates/ishtar/dashboards/dashboard_main.html +++ b/ishtar_common/templates/ishtar/dashboards/dashboard_main.html @@ -8,6 +8,7 @@  <script language="javascript" type="text/javascript" src="{{STATIC_URL}}js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>  <script language="javascript" type="text/javascript" src="{{STATIC_URL}}js/jqplot/plugins/jqplot.highlighter.min.js"></script>  <script language="javascript" type="text/javascript" src="{{STATIC_URL}}js/jqplot/plugins/jqplot.pieRenderer.min.js"></script> +<script language="javascript" type="text/javascript" src="{{STATIC_URL}}js/jqplot/plugins/jqplot.dateAxisRenderer.min.js"></script>  <link rel="stylesheet" href="{{STATIC_URL}}js/jqplot/jquery.jqplot.min.css" />  {% endblock %}  {% block content %} @@ -18,8 +19,9 @@ $(function() { $( "#dash-tabs" ).tabs(); });   <div id="dash-tabs">    <ul>      {% for label, app in app_list %} -   <li><a href="{% url 'dashboard-main-detail' app %}">{{label}}</a></li> -  {% endfor %}</ul> +   <li><a href="{% url 'dashboard-main-detail' app %}">{{label}}</a></li>{% endfor %} +   <li><a href="{% url 'dashboard-main-detail' 'users' %}">{% trans "Users" %}</a></li> +  </ul>   </div>  </div>  {% endblock %} diff --git a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html index 6a99c4fdd..610457ae3 100644 --- a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html +++ b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html @@ -1,38 +1,21 @@  {% load i18n %} -<div class='dashboard'> -<script language="javascript" type="text/javascript"> -$(document).ready(function(){ -var values_{{item_name}} = []; - -{% for idx, lbl, values in dashboard.values %} {% for value in values %} -{% ifequal forloop.parentloop.counter0 0 %}values_{{item_name}}.push([{{value}}, 0]); -{% else %}values_{{item_name}}[{{forloop.counter0}}][1] = {{value}};{% endifequal %}{% endfor%}{% endfor%} - -var plot_{{item_name}} = $.jqplot('chart_{{item_name}}', -    [values_{{item_name}}], { -      series:[{showMarker:false}], -      axes:{ -        xaxis:{ -          label:'{% trans "Year" %}' -        }, -        yaxis:{ -          label:'{% trans "Number"%}', -          min:0 -        } -      }, -      highlighter: { -        show: true, -        sizeAdjust: 7.5 -      } -  }); -}); -</script> -  <h3>{{lbl}}</h3> +{% load url from future %} +<div class='dashboard' id="{{unique_id}}-tab">    <div>      <h4>{% trans "Numbers" %}</h4>      <p><strong>{% trans "Total:" %}</strong> {{dashboard.total_number}}</p>      <div class='table'> -    <div id="chart_{{item_name}}" style="height:400px; width:700px;"></div> +    <div id="chart_{{unique_id}}" style="height:400px; width:700px;"></div> +    {% if form %} +    <div class='form'> +    <form method='post' action="{% url 'dashboard-main-detail' item_name %}" id='{{unique_id}}_form'> +      <table>{% csrf_token %} +        {{form}} +      </table> +      <button id="search_{{unique_id}}" class="submit">{% trans "Change" %}</button> +    </form> +    </div> +    {% endif %}      {% comment %}      <table>      {% for idx, lbl, values in dashboard.values %} @@ -43,7 +26,7 @@ var plot_{{item_name}} = $.jqplot('chart_{{item_name}}',      {% endfor%}      </table>{% endcomment %}      </div> -    {% if dashboard.years %} +    {% if dashboard.periods %}      <h4>{% trans "By years" %}</h4>      <ul>        <li><strong>{% trans "Average:" %}</strong> {{dashboard.average}}</li> @@ -65,7 +48,7 @@ var plot_{{item_name}} = $.jqplot('chart_{{item_name}}',      {% endif %}      <h4>{% trans "Created last" %}</h4>      <div class='table'> -    <table> +    <table class='resume'>        <tr><th>{{lbl}}</th><th>{% trans "Created" %}</th><th></th></tr>        {% for item in dashboard.lasts %}<tr>          <td class='ref'>{{item}}</td> @@ -76,7 +59,7 @@ var plot_{{item_name}} = $.jqplot('chart_{{item_name}}',      </div>      <h4>{% trans "Recent changes" %}</h4>      <div class='table'> -    <table> +    <table class='resume'>        <tr><th>{{lbl}}</th><th>{% trans "Modified" %}</th><th></th></tr>        {% for item in dashboard.recents %}<tr>          <td class='ref'>{{item}}</td> @@ -86,3 +69,64 @@ var plot_{{item_name}} = $.jqplot('chart_{{item_name}}',      </table>      </div>    </div> +<script language="javascript" type="text/javascript"> +$(document).ready(function(){ +var values_{{unique_id}} = []; + + +{% for idx, lbl, values in dashboard.values %} {% for value in values %}{% ifequal forloop.parentloop.counter0 0 %} +values_{{unique_id}}.push([{{VALUE_QUOTE|safe}}{{value}}{{VALUE_QUOTE|safe}}, 0]); +{% else %}values_{{unique_id}}[{{forloop.counter0}}][1] = {{value}};{% endifequal %}{% endfor%}{% endfor%} + +if (values_{{unique_id}}.length > 0){ + +var showmarker = false; +if (values_{{unique_id}}.length < 25){ +    var showmarker = true; +} + +var plot_{{unique_id}} = $.jqplot('chart_{{unique_id}}', +    [values_{{unique_id}}], { +      series:[{showMarker:showmarker}], +      axes:{ {%ifequal slicing 'year'%} +        xaxis:{ +          label:'{% trans "Year" %}', +          tickOptions: { +            formatString: "%d" +          } +        },{%endifequal%}{%ifequal slicing 'month'%} +        xaxis:{ +          label:'{% trans "Month" %}', +          renderer:$.jqplot.DateAxisRenderer, +          tickOptions:{formatString:'%b %Y'}, +        },{%endifequal%} +        yaxis:{ +          label:'{% trans "Number"%}', +          min:0 +        } +      }, +      highlighter: { +        show: true, +        sizeAdjust: 7.5 +      } +  }); + +} else { +$('#chart_{{unique_id}}').html("<p class='alert'>{% trans 'No data for theses criteria.' %}</p"); +} + +$('#search_{{unique_id}}').click(function (){ +    $.post("{% url 'dashboard-main-detail' item_name %}", +            $("#{{unique_id}}_form").serialize(), +            function(data){ +                $("#{{unique_id}}-tab").parent().html(data); +            }); +    return false; +}); + +{% ifequal item_name 'files' %} +load_jquerydate_after(); +load_jquerydate_before(); +{% endifequal %} +}); +</script> diff --git a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail_users.html b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail_users.html index 6602d5193..6ffd8c3c9 100644 --- a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail_users.html +++ b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail_users.html @@ -1,5 +1,5 @@  {% load i18n %} -  <h3>{% trans "Users" %}</h3> +<div class='dashboard'>    <div>  <script language="javascript" type="text/javascript">  $(document).ready(function(){ @@ -15,14 +15,14 @@ var plot_users = jQuery.jqplot ('user_chart', [values_users],            showDataLabels: true          }        }, -      legend: { show:true, location: 's' } +      legend: { show:true, location: 'e' }      }   );  });  </script> -    <div id="user_chart" style="height:400px; width:700px;"></div> +    <div id="user_chart" style="height:300px; width:700px;"></div>      <div class='table'> -    <table> +    <table class='resume'>        <tr><th>{% trans "User type" %}</th><th>{% trans "Number" %}</th></tr>      {% for user_type in ishtar_users.types %}        <tr> diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 9f193fdec..e51704794 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -703,35 +703,23 @@ def dashboard_main(request, dct, obj_id=None, *args, **kwargs):      """      Main dashboard      """ -    """ -    items = [] -    if 'archaeological_files' in settings.INSTALLED_APPS: -        from archaeological_files.models import File -        items.append((_(u"Archaeological files"), -                     models.Dashboard(File))) -    from archaeological_operations.models import Operation -    items.append((_(u"Operations"), models.Dashboard(Operation))) -    if 'archaeological_context_records' in settings.INSTALLED_APPS: -        from archaeological_context_records.models import ContextRecord -        items.append((_(u"Context records"), models.Dashboard(ContextRecord))) -    if 'archaeological_finds' in settings.INSTALLED_APPS: -        from archaeological_finds.models import Find -        items.append((_(u"Finds"), models.Dashboard(Find))) -    dct = {'items':items, -           'ishtar_users':models.UserDashboard()} -    """      app_list = []      if 'archaeological_files' in settings.INSTALLED_APPS:          app_list.append((_(u"Archaeological files"), 'files'))      app_list.append((_(u"Operations"), 'operations'))      if 'archaeological_context_records' in settings.INSTALLED_APPS: -        app_list.append((_(u"Context records"), 'context-records')) +        app_list.append((_(u"Context records"), 'contextrecords'))      if 'archaeological_finds' in settings.INSTALLED_APPS:          app_list.append((_(u"Finds"), 'finds'))      dct = {'app_list':app_list}      return render_to_response('ishtar/dashboards/dashboard_main.html', dct,                                 context_instance=RequestContext(request)) +DASHBOARD_FORMS = {} +if 'archaeological_files' in settings.INSTALLED_APPS: +    from archaeological_files.forms import DashboardForm as DashboardFormFile +    DASHBOARD_FORMS['files'] = DashboardFormFile +  def dashboard_main_detail(request, item_name):      """      Specific tab of the main dashboard @@ -741,25 +729,44 @@ def dashboard_main_detail(request, item_name):          return render_to_response(                      'ishtar/dashboards/dashboard_main_detail_users.html',                              dct, context_instance=RequestContext(request)) +    form = None +    slicing, fltr  = 'year', {} +    if item_name in DASHBOARD_FORMS: +        if request.method == 'POST': +            form = DASHBOARD_FORMS[item_name](request.POST) +            if form.is_valid(): +                slicing = form.cleaned_data['slicing'] +                fltr = form.get_filter() +        else: +            form = DASHBOARD_FORMS[item_name]()      lbl, dashboard = None, None      if item_name == 'files' and \        'archaeological_files' in settings.INSTALLED_APPS:          from archaeological_files.models import File -        lbl, dashboard = (_(u"Archaeological files"), models.Dashboard(File)) +        lbl, dashboard = (_(u"Archaeological files"), models.Dashboard(File, +                                                    slice=slicing, fltr=fltr))      if item_name == 'operations':          from archaeological_operations.models import Operation -        lbl, dashboard = (_(u"Operations"), models.Dashboard(Operation)) -    if item_name == 'context-records' and \ +        lbl, dashboard = (_(u"Operations"), models.Dashboard(Operation, +                                                    slice=slicing, fltr=fltr)) +    if item_name == 'contextrecords' and \        'archaeological_context_records' in settings.INSTALLED_APPS:          from archaeological_context_records.models import ContextRecord -        lbl, dashboard = (_(u"Context records"), models.Dashboard(ContextRecord)) +        lbl, dashboard = (_(u"Context records"), models.Dashboard(ContextRecord, +                                                    slice=slicing, fltr=fltr))      if item_name == 'finds' and \         'archaeological_finds' in settings.INSTALLED_APPS:          from archaeological_finds.models import Find -        lbl, dashboard = (_(u"Finds"), models.Dashboard(Find)) +        lbl, dashboard = (_(u"Finds"), models.Dashboard(Find, +                                                    slice=slicing, fltr=fltr))      if not lbl:          raise Http404      dct = {'lbl':lbl, 'dashboard':dashboard, -           'item_name':item_name.replace('-', '_')} +           'item_name':item_name.replace('-', '_'), +           'VALUE_QUOTE': '' if slicing == "year" else "'", +           'form':form, 'slicing':slicing} +    n = datetime.datetime.now() +    dct['unique_id'] = dct['item_name'] + "_" + \ +                '%d_%d_%d' % (n.minute, n.second, n.microsecond)      return render_to_response('ishtar/dashboards/dashboard_main_detail.html',                              dct, context_instance=RequestContext(request)) diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 71d88d062..e0556a7b7 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -1,326 +1,332 @@ -#!/usr/bin/env python
 -# -*- coding: utf-8 -*-
 -# Copyright (C) 2010-2013 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet>
 -# Copyright (C) 2007  skam <massimo dot scamarcia at gmail.com>
 -#                          (http://djangosnippets.org/snippets/233/)
 -
 -# This program is free software: you can redistribute it and/or modify
 -# it under the terms of the GNU Affero General Public License as
 -# published by the Free Software Foundation, either version 3 of the
 -# License, or (at your option) any later version.
 -
 -# This program is distributed in the hope that it will be useful,
 -# but WITHOUT ANY WARRANTY; without even the implied warranty of
 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 -# GNU Affero General Public License for more details.
 -
 -# You should have received a copy of the GNU Affero General Public License
 -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -
 -# See the file COPYING for details.
 -
 -from django import forms
 -from django.conf import settings
 -from django.core.urlresolvers import resolve, reverse
 -from django.db.models import fields
 -from django.forms import ClearableFileInput
 -from django.forms.widgets import flatatt
 -from django.template import Context, loader
 -from django.utils.encoding import smart_unicode
 -from django.utils.functional import lazy
 -from django.utils.html import escape
 -from django.utils.safestring import mark_safe
 -from django.utils.simplejson import JSONEncoder
 -from django.utils.translation import ugettext_lazy as _
 -
 -import models
 -
 -reverse_lazy = lazy(reverse, unicode)
 -
 -class MultipleAutocompleteField(forms.MultipleChoiceField):
 -    def __init__(self, *args, **kwargs):
 -        model = None
 -        if 'model' in kwargs:
 -            model = kwargs.pop('model')
 -        if 'choices' not in kwargs and model:
 -            kwargs['choices'] = [(i.pk, unicode(i))for i in model.objects.all()]
 -        new = kwargs.pop('new') if 'new' in kwargs else None
 -        if 'widget' not in kwargs and model:
 -            kwargs['widget'] = JQueryAutoComplete(reverse_lazy(
 -                    'autocomplete-'+model.__name__.lower()),
 -                    associated_model=model, new=new,
 -                    multiple=True)
 -        super(MultipleAutocompleteField, self).__init__(*args, **kwargs)
 -
 -    def clean(self, value):
 -        if value:
 -            # clean JS messup with values
 -            try:
 -                if type(value) not in (list, tuple):
 -                    value = [value]
 -                else:
 -                    val = value
 -                    value = []
 -                    for v in val:
 -                        v = unicode(v).strip('[').strip(']'
 -                                     ).strip('u').strip("'").strip('"')
 -                        value += [int(v.strip())
 -                                            for v in list(set(v.split(',')))
 -                                                                if v.strip()]
 -            except (TypeError, ValueError):
 -                value = []
 -        else:
 -            value = []
 -        return super(MultipleAutocompleteField, self).clean(value)
 -
 -class DeleteWidget(forms.CheckboxInput):
 -    def render(self, name, value, attrs=None):
 -        final_attrs = flatatt(self.build_attrs(attrs, name=name,
 -                                               value='1'))
 -        output = ['<tr class="delete"><td colspan="2">']
 -        output.append(u"<button%s>%s</button>" % (final_attrs, _("Delete")))
 -        output.append('</td></tr>')
 -        return mark_safe('\n'.join(output))
 -
 -class ImageFileInput(ClearableFileInput):
 -    template_with_initial = u'<span class="prettyPhoto">%(initial)s</span>'\
 -                      u' %(clear_template)s<br />%(input_text)s: %(input)s'
 -
 -class SquareMeterWidget(forms.TextInput):
 -    def render(self, name, value, attrs=None):
 -        if not value:
 -            value = u""
 -        final_attrs = flatatt(self.build_attrs(attrs, name=name, value=value))
 -        dct = {'final_attrs':final_attrs,
 -               'unit':settings.SURFACE_UNIT_LABEL,
 -               'id':attrs['id'],
 -               "safe_id":attrs['id'].replace('-', '_')}
 -        t = loader.get_template('blocks/SquareMeterWidget.html')
 -        rendered = t.render(Context(dct))
 -        return mark_safe(rendered)
 -
 -AreaWidget = forms.TextInput
 -if settings.SURFACE_UNIT == 'square-metre':
 -    global AreaWidget
 -    AreaWidget = SquareMeterWidget
 -
 -class JQueryDate(forms.TextInput):
 -    def __init__(self, *args, **kwargs):
 -        super(JQueryDate, self).__init__(*args, **kwargs)
 -        if 'class' not in self.attrs:
 -            self.attrs['class'] = ''
 -        self.attrs['class'] = 'date-pickup'
 -
 -    def render(self, name, value=None, attrs=None):
 -        rendered = super(JQueryDate, self).render(name, value, attrs)
 -        # use window.onload to be sure that datepicker don't interfere
 -        # with autocomplete fields
 -        rendered += """
 -<script type="text/javascript"><!--//
 -    $(window).load(function() {
 -        $(".date-pickup").datepicker($.datepicker.regional["%(country)s"]);
 -        var val = $("#id_%(name)s").val();
 -        if(val){
 -            var dt = $.datepicker.parseDate('yy-mm-dd', val);
 -            val = $.datepicker.formatDate(
 -                    $.datepicker.regional["%(country)s"]['dateFormat'],
 -                    dt);
 -            $("#id_%(name)s").val(val);
 -        }
 -    });
 -//--></script>
 -""" % {"name":name, "country":settings.COUNTRY}
 -        return rendered
 -
 -class JQueryAutoComplete(forms.TextInput):
 -    def __init__(self, source, associated_model=None, options={}, attrs={},
 -                 new=False, multiple=False, limit={}):
 -        """
 -        Source can be a list containing the autocomplete values or a
 -        string containing the url used for the request.
 -        """
 -        self.options = None
 -        self.attrs = {}
 -        self.source = source
 -        self.associated_model = associated_model
 -        if len(options) > 0:
 -            self.options = JSONEncoder().encode(options)
 -        self.attrs.update(attrs)
 -        self.new = new
 -        self.multiple = multiple
 -        self.limit = limit
 -
 -    def value_from_datadict(self, data, files, name):
 -        if self.multiple:
 -            return data.getlist(name, None)
 -        else:
 -            return data.get(name, None)
 -
 -    def render_js(self, field_id):
 -        if isinstance(self.source, list):
 -            source = JSONEncoder().encode(self.source)
 -        elif isinstance(self.source, str) or isinstance(self.source, unicode):
 -            source = "'%s'" % escape(self.source)
 -        else:
 -            try:
 -                source = "'" + unicode(self.source) + "'"
 -            except:
 -                raise ValueError('source type is not valid')
 -        dct = {'source':mark_safe(source),
 -               'field_id':field_id}
 -        if self.options:
 -            dct['options'] = mark_safe('%s' % self.options)
 -
 -        js = ""
 -        tpl = 'blocks/JQueryAutocomplete.js'
 -        if self.multiple:
 -            tpl = 'blocks/JQueryAutocompleteMultiple.js'
 -        t = loader.get_template(tpl)
 -        js = t.render(Context(dct))
 -        return js
 -
 -    def render(self, name, value=None, attrs=None):
 -        attrs_hidden = self.build_attrs(attrs, name=name)
 -        attrs_select = self.build_attrs(attrs)
 -        if value:
 -            hiddens = []
 -            selects = []
 -            values = value
 -            if type(value) not in (list, tuple):
 -                values = unicode(escape(smart_unicode(value)))
 -                values = values.replace('[', '').replace(']', '')
 -                values = values.split(',')
 -            else:
 -                values = []
 -                for v in value:
 -                    values += v.split(',')
 -            for v in values:
 -                if not v:
 -                    continue
 -                hiddens.append(v)
 -                selects.append(v)
 -                if self.associated_model:
 -                    try:
 -                        selects[-1] = unicode(
 -                            self.associated_model.objects.get(pk=v))
 -                    except (self.associated_model.DoesNotExist, ValueError):
 -                        selects.pop()
 -                        hiddens.pop()
 -            if self.multiple:
 -                attrs_hidden['value'] = ", ".join(hiddens)
 -                if selects:
 -                    selects.append("")
 -                attrs_select['value'] = ", ".join(selects)
 -            else:
 -                if hiddens and selects:
 -                    attrs_hidden['value'] = hiddens[0]
 -                    attrs_select['value'] = selects[0]
 -        if not self.attrs.has_key('id'):
 -            attrs_hidden['id'] = 'id_%s' % name
 -            attrs_select['id'] = 'id_select_%s' % name
 -        if 'class' not in attrs_select:
 -            attrs_select['class'] = 'autocomplete'
 -        new = ''
 -        if self.new:
 -            model_name = self.associated_model._meta.object_name.lower()
 -            limits = []
 -            for k in self.limit:
 -                limits.append(k + "__" + "-".join(
 -                            [unicode(v) for v in self.limit[k]]))
 -            args = [attrs_select['id']]
 -            if limits:
 -                args.append(';'.join(limits))
 -            url_new = reverse('new-' + model_name, args=args)
 -            new = u'  <a href="#" class="add-button" '\
 -                  u'onclick="open_window(\'%s\');">+</a>' % url_new
 -        html = u'''<input%(attrs_select)s/>%(new)s\
 -<input type="hidden"%(attrs_hidden)s/>\
 -        <script type="text/javascript"><!--//
 -        $(function() {%(js)s});//--></script>
 -        ''' % {
 -            'attrs_select' : flatatt(attrs_select),
 -            'attrs_hidden' : flatatt(attrs_hidden),
 -            'js' : self.render_js(name),
 -            'new':new
 -        }
 -        return html
 -
 -class JQueryJqGrid(forms.RadioSelect):
 -    COL_TPL = "{name:'%(idx)s', index:'%(idx)s', sortable:true}"
 -    class Media:
 -        js = ['%s/js/i18n/grid.locale-%s.js' % (settings.STATIC_URL,
 -                                                settings.COUNTRY),
 -              '%s/js/jquery.jqGrid.min.js' % settings.STATIC_URL,
 -             ]
 -        css = {'all':['%s/media/ui.jqgrid.css' % settings.STATIC_URL,
 -                     ]}
 -
 -    def __init__(self, source, form, associated_model, attrs={},
 -         table_cols='TABLE_COLS', multiple=False, multiple_cols=[2], new=False,
 -         new_message="", source_full=None):
 -        self.source = source
 -        self.form = form
 -        self.attrs = attrs
 -        self.associated_model = associated_model
 -        self.table_cols = table_cols
 -        self.multiple = multiple
 -        self.multiple_cols = multiple_cols
 -        self.new, self.new_message = new, new_message
 -        self.source_full = source_full
 -
 -    def render(self, name, value=None, attrs=None):
 -        t = loader.get_template('blocks/form_snippet.html')
 -        form = self.form()
 -        rendered = t.render(Context({'form':form}))
 -        dct = {}
 -        if self.new:
 -            model_name = self.associated_model._meta.object_name.lower()
 -            dct['url_new'] = reverse('new-' + model_name, args=['0'])
 -            dct['new_message'] = self.new_message
 -        extra_cols = []
 -        col_names, col_idx = [], []
 -        for k in form.get_input_ids():
 -            #field = form.fields[k]
 -            col_idx.append(u'"%s"' % k)
 -        for field_name in getattr(self.associated_model, self.table_cols):
 -            field = self.associated_model
 -            keys = field_name.split('.')
 -            field_verbose_name = ""
 -            for key in keys:
 -                if hasattr(field, 'rel'):
 -                    field = field.rel.to
 -                try:
 -                    field = field._meta.get_field(key)
 -                    field_verbose_name = field.verbose_name
 -                    field_name = field.name
 -                except fields.FieldDoesNotExist:
 -                    if hasattr(field, key + '_lbl'):
 -                        field_name = key
 -                        field_verbose_name = getattr(field, key + '_lbl')
 -                    else:
 -                        continue
 -            col_names.append(u'"%s"' % field_verbose_name)
 -            extra_cols.append(self.COL_TPL % {'idx':field_name})
 -        col_names = col_names and ", ".join(col_names) or ""
 -        col_idx = col_idx and ", ".join(col_idx) or ""
 -        extra_cols = extra_cols and ", ".join(extra_cols) or ""
 -        dct['encoding'] = settings.ENCODING or 'utf-8'
 -        dct['source'] = unicode(self.source)
 -        if unicode(self.source_full) and unicode(self.source_full) != 'None':
 -            dct['source_full'] = unicode(self.source_full)
 -        dct.update({'name':name,
 -               'col_names':col_names,
 -               'extra_cols':extra_cols,
 -               'source':unicode(self.source),
 -               'col_idx':col_idx,
 -               'no_result':unicode(_("No results")),
 -               'loading':unicode(_("Loading...")),
 -               'remove':unicode(_(u"Remove")),
 -               'sname':name.replace('-', ''),
 -               'multiple':self.multiple,
 -               'multi_cols': ",".join((u'"%d"' % col \
 -                                       for col in self.multiple_cols))
 -              })
 -        t = loader.get_template('blocks/JQueryJqGrid.html')
 -        rendered += t.render(Context(dct))
 -        return mark_safe(rendered)
 +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2014 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2007  skam <massimo dot scamarcia at gmail.com> +#                          (http://djangosnippets.org/snippets/233/) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +from django import forms +from django.conf import settings +from django.core.urlresolvers import resolve, reverse +from django.db.models import fields +from django.forms import ClearableFileInput +from django.forms.widgets import flatatt +from django.template import Context, loader +from django.utils.encoding import smart_unicode +from django.utils.functional import lazy +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.simplejson import JSONEncoder +from django.utils.translation import ugettext_lazy as _ + +import models + +reverse_lazy = lazy(reverse, unicode) + +class MultipleAutocompleteField(forms.MultipleChoiceField): +    def __init__(self, *args, **kwargs): +        model = None +        if 'model' in kwargs: +            model = kwargs.pop('model') +        if 'choices' not in kwargs and model: +            kwargs['choices'] = [(i.pk, unicode(i))for i in model.objects.all()] +        new = kwargs.pop('new') if 'new' in kwargs else None +        if 'widget' not in kwargs and model: +            kwargs['widget'] = JQueryAutoComplete(reverse_lazy( +                    'autocomplete-'+model.__name__.lower()), +                    associated_model=model, new=new, +                    multiple=True) +        super(MultipleAutocompleteField, self).__init__(*args, **kwargs) + +    def clean(self, value): +        if value: +            # clean JS messup with values +            try: +                if type(value) not in (list, tuple): +                    value = [value] +                else: +                    val = value +                    value = [] +                    for v in val: +                        v = unicode(v).strip('[').strip(']' +                                     ).strip('u').strip("'").strip('"') +                        value += [int(v.strip()) +                                            for v in list(set(v.split(','))) +                                                                if v.strip()] +            except (TypeError, ValueError): +                value = [] +        else: +            value = [] +        return super(MultipleAutocompleteField, self).clean(value) + +class DeleteWidget(forms.CheckboxInput): +    def render(self, name, value, attrs=None): +        final_attrs = flatatt(self.build_attrs(attrs, name=name, +                                               value='1')) +        output = ['<tr class="delete"><td colspan="2">'] +        output.append(u"<button%s>%s</button>" % (final_attrs, _("Delete"))) +        output.append('</td></tr>') +        return mark_safe('\n'.join(output)) + +class ImageFileInput(ClearableFileInput): +    template_with_initial = u'<span class="prettyPhoto">%(initial)s</span>'\ +                      u' %(clear_template)s<br />%(input_text)s: %(input)s' + +class SquareMeterWidget(forms.TextInput): +    def render(self, name, value, attrs=None): +        if not value: +            value = u"" +        final_attrs = flatatt(self.build_attrs(attrs, name=name, value=value)) +        dct = {'final_attrs':final_attrs, +               'unit':settings.SURFACE_UNIT_LABEL, +               'id':attrs['id'], +               "safe_id":attrs['id'].replace('-', '_')} +        t = loader.get_template('blocks/SquareMeterWidget.html') +        rendered = t.render(Context(dct)) +        return mark_safe(rendered) + +AreaWidget = forms.TextInput +if settings.SURFACE_UNIT == 'square-metre': +    #global AreaWidget +    AreaWidget = SquareMeterWidget + +class JQueryDate(forms.TextInput): +    def __init__(self, *args, **kwargs): +        super(JQueryDate, self).__init__(*args, **kwargs) +        if 'class' not in self.attrs: +            self.attrs['class'] = '' +        self.attrs['class'] = 'date-pickup' + +    def render(self, name, value=None, attrs=None): +        # very specific... +        if settings.COUNTRY == 'fr' and value and '/' in value: +            values = value.split('/') +            if len(values) == 3: +                value = "%s-%s-%s" % (values[2], values[1], values[0]) +        rendered = super(JQueryDate, self).render(name, value, attrs) +        # use window.onload to be sure that datepicker don't interfere +        # with autocomplete fields +        rendered += """ +<script type="text/javascript"><!--// +    function load_jquerydate_%(name)s(){ +        $(".date-pickup").datepicker($.datepicker.regional["%(country)s"]); +        var val = $("#id_%(name)s").val(); +        if(val){ +            var dt = $.datepicker.parseDate('yy-mm-dd', val); +            val = $.datepicker.formatDate( +                    $.datepicker.regional["%(country)s"]['dateFormat'], +                    dt); +            $("#id_%(name)s").val(val); +        } +    } +    $(window).load(load_jquerydate_%(name)s); +//--></script> +""" % {"name":name, "country":settings.COUNTRY} +        return rendered + +class JQueryAutoComplete(forms.TextInput): +    def __init__(self, source, associated_model=None, options={}, attrs={}, +                 new=False, multiple=False, limit={}): +        """ +        Source can be a list containing the autocomplete values or a +        string containing the url used for the request. +        """ +        self.options = None +        self.attrs = {} +        self.source = source +        self.associated_model = associated_model +        if len(options) > 0: +            self.options = JSONEncoder().encode(options) +        self.attrs.update(attrs) +        self.new = new +        self.multiple = multiple +        self.limit = limit + +    def value_from_datadict(self, data, files, name): +        if self.multiple: +            return data.getlist(name, None) +        else: +            return data.get(name, None) + +    def render_js(self, field_id): +        if isinstance(self.source, list): +            source = JSONEncoder().encode(self.source) +        elif isinstance(self.source, str) or isinstance(self.source, unicode): +            source = "'%s'" % escape(self.source) +        else: +            try: +                source = "'" + unicode(self.source) + "'" +            except: +                raise ValueError('source type is not valid') +        dct = {'source':mark_safe(source), +               'field_id':field_id} +        if self.options: +            dct['options'] = mark_safe('%s' % self.options) + +        js = "" +        tpl = 'blocks/JQueryAutocomplete.js' +        if self.multiple: +            tpl = 'blocks/JQueryAutocompleteMultiple.js' +        t = loader.get_template(tpl) +        js = t.render(Context(dct)) +        return js + +    def render(self, name, value=None, attrs=None): +        attrs_hidden = self.build_attrs(attrs, name=name) +        attrs_select = self.build_attrs(attrs) +        if value: +            hiddens = [] +            selects = [] +            values = value +            if type(value) not in (list, tuple): +                values = unicode(escape(smart_unicode(value))) +                values = values.replace('[', '').replace(']', '') +                values = values.split(',') +            else: +                values = [] +                for v in value: +                    values += v.split(',') +            for v in values: +                if not v: +                    continue +                hiddens.append(v) +                selects.append(v) +                if self.associated_model: +                    try: +                        selects[-1] = unicode( +                            self.associated_model.objects.get(pk=v)) +                    except (self.associated_model.DoesNotExist, ValueError): +                        selects.pop() +                        hiddens.pop() +            if self.multiple: +                attrs_hidden['value'] = ", ".join(hiddens) +                if selects: +                    selects.append("") +                attrs_select['value'] = ", ".join(selects) +            else: +                if hiddens and selects: +                    attrs_hidden['value'] = hiddens[0] +                    attrs_select['value'] = selects[0] +        if not self.attrs.has_key('id'): +            attrs_hidden['id'] = 'id_%s' % name +            attrs_select['id'] = 'id_select_%s' % name +        if 'class' not in attrs_select: +            attrs_select['class'] = 'autocomplete' +        new = '' +        if self.new: +            model_name = self.associated_model._meta.object_name.lower() +            limits = [] +            for k in self.limit: +                limits.append(k + "__" + "-".join( +                            [unicode(v) for v in self.limit[k]])) +            args = [attrs_select['id']] +            if limits: +                args.append(';'.join(limits)) +            url_new = reverse('new-' + model_name, args=args) +            new = u'  <a href="#" class="add-button" '\ +                  u'onclick="open_window(\'%s\');">+</a>' % url_new +        html = u'''<input%(attrs_select)s/>%(new)s\ +<input type="hidden"%(attrs_hidden)s/>\ +        <script type="text/javascript"><!--// +        $(function() {%(js)s});//--></script> +        ''' % { +            'attrs_select' : flatatt(attrs_select), +            'attrs_hidden' : flatatt(attrs_hidden), +            'js' : self.render_js(name), +            'new':new +        } +        return html + +class JQueryJqGrid(forms.RadioSelect): +    COL_TPL = "{name:'%(idx)s', index:'%(idx)s', sortable:true}" +    class Media: +        js = ['%s/js/i18n/grid.locale-%s.js' % (settings.STATIC_URL, +                                                settings.COUNTRY), +              '%s/js/jquery.jqGrid.min.js' % settings.STATIC_URL, +             ] +        css = {'all':['%s/media/ui.jqgrid.css' % settings.STATIC_URL, +                     ]} + +    def __init__(self, source, form, associated_model, attrs={}, +         table_cols='TABLE_COLS', multiple=False, multiple_cols=[2], new=False, +         new_message="", source_full=None): +        self.source = source +        self.form = form +        self.attrs = attrs +        self.associated_model = associated_model +        self.table_cols = table_cols +        self.multiple = multiple +        self.multiple_cols = multiple_cols +        self.new, self.new_message = new, new_message +        self.source_full = source_full + +    def render(self, name, value=None, attrs=None): +        t = loader.get_template('blocks/form_snippet.html') +        form = self.form() +        rendered = t.render(Context({'form':form})) +        dct = {} +        if self.new: +            model_name = self.associated_model._meta.object_name.lower() +            dct['url_new'] = reverse('new-' + model_name, args=['0']) +            dct['new_message'] = self.new_message +        extra_cols = [] +        col_names, col_idx = [], [] +        for k in form.get_input_ids(): +            #field = form.fields[k] +            col_idx.append(u'"%s"' % k) +        for field_name in getattr(self.associated_model, self.table_cols): +            field = self.associated_model +            keys = field_name.split('.') +            field_verbose_name = "" +            for key in keys: +                if hasattr(field, 'rel'): +                    field = field.rel.to +                try: +                    field = field._meta.get_field(key) +                    field_verbose_name = field.verbose_name +                    field_name = field.name +                except fields.FieldDoesNotExist: +                    if hasattr(field, key + '_lbl'): +                        field_name = key +                        field_verbose_name = getattr(field, key + '_lbl') +                    else: +                        continue +            col_names.append(u'"%s"' % field_verbose_name) +            extra_cols.append(self.COL_TPL % {'idx':field_name}) +        col_names = col_names and ", ".join(col_names) or "" +        col_idx = col_idx and ", ".join(col_idx) or "" +        extra_cols = extra_cols and ", ".join(extra_cols) or "" +        dct['encoding'] = settings.ENCODING or 'utf-8' +        dct['source'] = unicode(self.source) +        if unicode(self.source_full) and unicode(self.source_full) != 'None': +            dct['source_full'] = unicode(self.source_full) +        dct.update({'name':name, +               'col_names':col_names, +               'extra_cols':extra_cols, +               'source':unicode(self.source), +               'col_idx':col_idx, +               'no_result':unicode(_("No results")), +               'loading':unicode(_("Loading...")), +               'remove':unicode(_(u"Remove")), +               'sname':name.replace('-', ''), +               'multiple':self.multiple, +               'multi_cols': ",".join((u'"%d"' % col \ +                                       for col in self.multiple_cols)) +              }) +        t = loader.get_template('blocks/JQueryJqGrid.html') +        rendered += t.render(Context(dct)) +        return mark_safe(rendered)  | 
