Define a custom projects list view (closes #2723082)

  • add a project_icon relation
  • define the projects list view as a SameETypeListView where entities are sorted by modification date and displayed along with a project icon
  • define specific CSS for the view
  • add a default project icon file
authorDenis Laxalde <denis.laxalde@logilab.fr>
changeset3155321b7fac
branchdefault
phasedraft
hiddenyes
parent revision#a7c380494802 [merge] backport stable
child revision#cec0782424cc Add hooks to projects' modification_date upon ticket/version/subproject events
files modified by this revision
__pkginfo__.py
data/cubes.tracker.css
data/project-icon.png
data/project-icon.svg
i18n/en.po
i18n/es.po
i18n/fr.po
migration/1.11.2_Any.py
schema.py
views/project.py
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1361981790 -3600
# Wed Feb 27 17:16:30 2013 +0100
# Node ID 3155321b7fac64feb04f63efeef6b2f8de03abef
# Parent a7c38049480224367a77397f34fab4001d253dd5
Define a custom projects list view (closes #2723082)

- add a project_icon relation
- define the projects list view as a SameETypeListView where entities are
sorted by modification date and displayed along with a project icon
- define specific CSS for the view
- add a default project icon file

diff --git a/__pkginfo__.py b/__pkginfo__.py
@@ -25,10 +25,11 @@
1 
2  __depends__ = {'cubicweb': '>= 3.15.0',
3                 'cubicweb-activitystream': None,
4                 'cubicweb-localperms': None,
5                 'cubicweb-iprogress': None,
6 +               'cubicweb-file': None,
7                 }
8  __recommends__ = {'cubicweb-preview': None}
9 
10  # packaging ###
11 
diff --git a/data/cubes.tracker.css b/data/cubes.tracker.css
@@ -24,5 +24,35 @@
12  }
13  .important {
14      padding: 1px 0px 1px 18px;
15      background: url("priority_important.png") 0% 0.2em no-repeat !important;
16  }
17 +
18 +div.project-item {
19 +    clear: both;
20 +    margin-bottom: 10px;
21 +    height: 70px;
22 +}
23 +
24 +div.project-item div.iconwrapper {
25 +    float: left;
26 +    height: 50px;
27 +    position: relative;
28 +}
29 +
30 +div.project-item div.iconwrapper img {
31 +    position: absolute;
32 +    clip: rect(2px, 50px, 50px, 2px);
33 +}
34 +
35 +div.project-item div.textwrapper {
36 +    padding-left: 64px;
37 +}
38 +
39 +div.project-item div.textwrapper div.summary {
40 +    color: #707070;
41 +    font-style: italic;
42 +}
43 +
44 +div.project-item div.textwrapper div.updated {
45 +    font-size: 90%;
46 +}
diff --git a/data/project-icon.png b/data/project-icon.png
diff --git a/data/project-icon.svg b/data/project-icon.svg
@@ -0,0 +1,102 @@
47 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
48 +<!-- Created with Inkscape (http://www.inkscape.org/) -->
49 +
50 +<svg
51 +   xmlns:dc="http://purl.org/dc/elements/1.1/"
52 +   xmlns:cc="http://creativecommons.org/ns#"
53 +   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
54 +   xmlns:svg="http://www.w3.org/2000/svg"
55 +   xmlns="http://www.w3.org/2000/svg"
56 +   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
57 +   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
58 +   width="96"
59 +   height="96"
60 +   id="svg3723"
61 +   version="1.1"
62 +   inkscape:version="0.47 r22583"
63 +   sodipodi:docname="cube.svg"
64 +   inkscape:export-filename="/home/dlaxalde/src/cw/cubes/tracker/data/project-icon.png"
65 +   inkscape:export-xdpi="45"
66 +   inkscape:export-ydpi="45">
67 +  <defs
68 +     id="defs3725">
69 +    <inkscape:perspective
70 +       sodipodi:type="inkscape:persp3d"
71 +       inkscape:vp_x="0 : 526.18109 : 1"
72 +       inkscape:vp_y="0 : 1000 : 0"
73 +       inkscape:vp_z="744.09448 : 526.18109 : 1"
74 +       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
75 +       id="perspective3731" />
76 +    <inkscape:perspective
77 +       id="perspective3762"
78 +       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
79 +       inkscape:vp_z="1 : 0.5 : 1"
80 +       inkscape:vp_y="0 : 1000 : 0"
81 +       inkscape:vp_x="0 : 0.5 : 1"
82 +       sodipodi:type="inkscape:persp3d" />
83 +  </defs>
84 +  <sodipodi:namedview
85 +     id="base"
86 +     pagecolor="#ffffff"
87 +     bordercolor="#666666"
88 +     borderopacity="1.0"
89 +     inkscape:pageopacity="0.0"
90 +     inkscape:pageshadow="2"
91 +     inkscape:zoom="3.959798"
92 +     inkscape:cx="-48.284292"
93 +     inkscape:cy="24.156909"
94 +     inkscape:document-units="px"
95 +     inkscape:current-layer="layer1"
96 +     showgrid="false"
97 +     inkscape:window-width="1539"
98 +     inkscape:window-height="865"
99 +     inkscape:window-x="0"
100 +     inkscape:window-y="36"
101 +     inkscape:window-maximized="0" />
102 +  <metadata
103 +     id="metadata3728">
104 +    <rdf:RDF>
105 +      <cc:Work
106 +         rdf:about="">
107 +        <dc:format>image/svg+xml</dc:format>
108 +        <dc:type
109 +           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
110 +        <dc:title></dc:title>
111 +      </cc:Work>
112 +    </rdf:RDF>
113 +  </metadata>
114 +  <g
115 +     inkscape:label="Layer 1"
116 +     inkscape:groupmode="layer"
117 +     id="layer1"
118 +     transform="translate(-458.31293,-517.04397)">
119 +    <g
120 +       id="g2819"
121 +       transform="matrix(0.89341615,0,0,0.89341615,58.25107,62.905909)">
122 +      <path
123 +         d="m 461.8107,580.93689 39.67087,26.33646 39.73831,-26.41583 -39.66809,-22.91924 z"
124 +         style="fill:none;stroke:none"
125 +         id="path3692-6" />
126 +      <path
127 +         d="m 461.8107,536.95786 0,43.97903 39.74109,-22.99861 0,-41.12615 z"
128 +         style="fill:none;stroke:none"
129 +         id="path3694-9" />
130 +      <path
131 +         d="m 501.55179,516.81213 39.66809,20.07483 0,43.97056 -39.66809,-22.91924 z"
132 +         style="fill:none;stroke:none"
133 +         id="path3696-4" />
134 +      <path
135 +         d="m 461.8107,536.95786 39.67087,23.06805 39.73831,-23.13895 -39.66809,-20.07483 z"
136 +         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.94479203;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
137 +         id="path3698-9" />
138 +      <path
139 +         d="m 501.48157,560.02591 0,47.24744 39.73831,-26.41583 0,-43.97056 z"
140 +         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.94479203;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
141 +         id="path3700-4" />
142 +      <path
143 +         d="m 461.8107,536.95786 39.67087,23.06805 0,47.24744 -39.67087,-26.33646 z"
144 +         style="fill:#ff8d15;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.94479203;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
145 +         id="path3702-9" />
146 +    </g>
147 +  </g>
148 +</svg>
diff --git a/i18n/en.po b/i18n/en.po
@@ -101,10 +101,14 @@
149  msgstr ""
150 
151  msgid "All current versions"
152  msgstr "All current versions"
153 
154 +msgctxt "inlined:Project.project_icon.subject"
155 +msgid "File"
156 +msgstr ""
157 +
158  msgid "Identical tickets"
159  msgstr ""
160 
161  msgid "New Project"
162  msgstr "New project"
@@ -194,10 +198,14 @@
163  msgstr "ticket"
164 
165  msgid "add Version version_of Project object"
166  msgstr "version"
167 
168 +msgctxt "inlined:Project.project_icon.subject"
169 +msgid "add a File"
170 +msgstr "add an icon"
171 +
172  msgid "appeared_in"
173  msgstr "appeared in"
174 
175  # subject and object forms for each relation type
176  # (no object form for final relation types)
@@ -515,10 +523,24 @@
177  msgstr "priority"
178 
179  msgid "project's dependencies"
180  msgstr ""
181 
182 +msgid "project_icon"
183 +msgstr ""
184 +
185 +msgctxt "Project"
186 +msgid "project_icon"
187 +msgstr ""
188 +
189 +msgid "project_icon_object"
190 +msgstr ""
191 +
192 +msgctxt "File"
193 +msgid "project_icon_object"
194 +msgstr ""
195 +
196  msgid "projectinfo_tab"
197  msgstr "project info"
198 
199  msgid "projectroadmap_tab"
200  msgstr "roadmap"
diff --git a/i18n/es.po b/i18n/es.po
@@ -95,10 +95,14 @@
201  msgstr ""
202 
203  msgid "All current versions"
204  msgstr ""
205 
206 +msgctxt "inlined:Project.project_icon.subject"
207 +msgid "File"
208 +msgstr ""
209 +
210  msgid "Identical tickets"
211  msgstr ""
212 
213  msgid "New Project"
214  msgstr ""
@@ -188,10 +192,14 @@
215  msgstr ""
216 
217  msgid "add Version version_of Project object"
218  msgstr ""
219 
220 +msgctxt "inlined:Project.project_icon.subject"
221 +msgid "add a File"
222 +msgstr ""
223 +
224  msgid "appeared_in"
225  msgstr ""
226 
227  # subject and object forms for each relation type
228  # (no object form for final relation types)
@@ -507,10 +515,24 @@
229  msgstr ""
230 
231  msgid "project's dependencies"
232  msgstr ""
233 
234 +msgid "project_icon"
235 +msgstr ""
236 +
237 +msgctxt "Project"
238 +msgid "project_icon"
239 +msgstr ""
240 +
241 +msgid "project_icon_object"
242 +msgstr ""
243 +
244 +msgctxt "File"
245 +msgid "project_icon_object"
246 +msgstr ""
247 +
248  msgid "projectinfo_tab"
249  msgstr ""
250 
251  msgid "projectroadmap_tab"
252  msgstr ""
diff --git a/i18n/fr.po b/i18n/fr.po
@@ -144,10 +144,14 @@
253  msgstr "<non plannifié>"
254 
255  msgid "All current versions"
256  msgstr "Toutes les versions en cours"
257 
258 +msgctxt "inlined:Project.project_icon.subject"
259 +msgid "File"
260 +msgstr ""
261 +
262  msgid "Identical tickets"
263  msgstr "Ticket identiques"
264 
265  msgid "New Project"
266  msgstr "Nouveau projet"
@@ -243,10 +247,14 @@
267  msgstr "ticket"
268 
269  msgid "add Version version_of Project object"
270  msgstr "version"
271 
272 +msgctxt "inlined:Project.project_icon.subject"
273 +msgid "add a File"
274 +msgstr "ajouter une icône"
275 +
276  msgid "appeared_in"
277  msgstr "apparu dans"
278 
279  # subject and object forms for each relation type
280  # (no object form for final relation types)
@@ -572,10 +580,24 @@
281  msgstr "priorité"
282 
283  msgid "project's dependencies"
284  msgstr "dépendances du projet"
285 
286 +msgid "project_icon"
287 +msgstr ""
288 +
289 +msgctxt "Project"
290 +msgid "project_icon"
291 +msgstr ""
292 +
293 +msgid "project_icon_object"
294 +msgstr ""
295 +
296 +msgctxt "File"
297 +msgid "project_icon_object"
298 +msgstr ""
299 +
300  msgid "projectinfo_tab"
301  msgstr "présentation"
302 
303  msgid "projectroadmap_tab"
304  msgstr "planification"
diff --git a/migration/1.11.2_Any.py b/migration/1.11.2_Any.py
@@ -0,0 +1,2 @@
305 +add_cube('file')
306 +#add_relation_type('project_icon')
diff --git a/schema.py b/schema.py
@@ -39,10 +39,12 @@
307                               description=_('more detailed description'))
308 
309      ## relations
310      uses = SubjectRelation('Project', description=_('project\'s dependencies'))
311      subproject_of = SubjectRelation('Project', composite='object', cardinality='?*')
312 +    project_icon = SubjectRelation('File', cardinality='??', composite='subject', inlined=True,
313 +                                   constraints=[RQLConstraint('O data_format like "image/%"')])
314 
315 
316  class uses(RelationType):
317      __permissions__ = {
318          'read':   ('managers', 'users', 'guests'),
diff --git a/views/project.py b/views/project.py
@@ -33,10 +33,58 @@
319 
320      tabs = [_('projectinfo_tab'), _('projectroadmap_tab'), _('projecttickets_tab'),
321              'activitystream']
322      default_tab = 'projectinfo_tab'
323 
324 +class ProjectListView(baseviews.SameETypeListView):
325 +    __select__ = (baseviews.SameETypeListView.__select__ &
326 +                  is_instance("Project"))
327 +    def call(self, **kwargs):
328 +        # sort by modification date
329 +        self.cw_rset = self.cw_rset.sorted_rset(lambda e: e.cw_attr_cache['modification_date'],
330 +                                                reverse=True)
331 +        super(ProjectListView, self).call(**kwargs)
332 +
333 +class ProjetListItemView(baseviews.SameETypeListItemView):
334 +    __select__ = (baseviews.SameETypeListItemView.__select__ &
335 +                  is_instance("Project"))
336 +
337 +    def cell_call(self, row, col, **kwargs):
338 +        self._cw.add_css('cubes.tracker.css')
339 +        entity = self.cw_rset.get_entity(row, col)
340 +        title = xml_escape(entity.dc_title())
341 +        updated = entity.cw_attr_cache['modification_date'].strftime('%F, %R')
342 +        if entity.project_icon:
343 +            icon = xml_escape(entity.project_icon[0].cw_adapt_to('IImage').download_url())
344 +        else:
345 +            icon = self._cw.data_url('project-icon.png')
346 +        # project icon
347 +        self.w(u'<div class="project-item">')
348 +        self.w(u'<div class="iconwrapper"><img alt="%(title)s" src="%(icon)s"/></div>'
349 +               % {'title': title, 'icon': icon})
350 +        self.w(u'<div class="textwrapper">')
351 +        # project title, last update date and maybe summary
352 +        self.w(u'<h3>')
353 +        self.wview('oneline', entity.as_rset())
354 +        self.w(u'</h3>')
355 +        self.w(u'<div class="updated">')
356 +        self.w(u'<p>')
357 +        self.w(u'updated on: %s' % updated)
358 +        self.w(u'</p>')
359 +        self.w(u'</div>')
360 +        if entity.summary:
361 +            self.w(u'<div class="summary">')
362 +            self.w(u'<p>')
363 +            self.w(xml_escape(entity.summary))
364 +            self.w(u'</p>')
365 +            self.w(u'</div>')
366 +        self.w(u'</div>')
367 +        self.w(u'</div>')
368 +
369 +# configure project form
370 +_afs = uicfg.autoform_section
371 +_afs.tag_subject_of(('Project', 'project_icon', 'File'), 'main', 'inlined')
372 
373  # configure projectinfotab
374  _pvs = uicfg.primaryview_section
375  _pvs.tag_attribute(('Project', 'name'), 'hidden')
376  # XXX keep '*', not Project to match other target types added in eg forge