PageRenderTime 69ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/psp/models.py

http://parliament-poll-stats.googlecode.com/
Python | 4721 lines | 4699 code | 1 blank | 21 comment | 6 complexity | 4ed341d9c73fe68daafddd46b79d14cc MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #coding=utf-8
  2. #suppress 'too many' lines warning
  3. #pylint: disable-msg=C0302
  4. """
  5. copyright (c) 2009, paketka@gmail.com et. al
  6. All rights reserved.
  7. Redistribution and use in source and binary forms, with or without
  8. modification, are permitted provided that the following conditions are met:
  9. * Redistributions of source code must retain the above copyright notice,
  10. this list of conditions and the following disclaimer.
  11. * Redistributions in binary form must reproduce the above copyright notice,
  12. this list of conditions and the following disclaimer in the documentation
  13. and/or other materials provided with the distribution.
  14. * Neither the name of the <ORGANIZATION> nor the names of its contributors
  15. may be used to endorse or promote products derived from this software
  16. without specific prior written permission.
  17. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  21. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  23. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  24. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  25. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. """
  28. import datetime
  29. import re
  30. from django.shortcuts import get_object_or_404
  31. from django.db import models
  32. from django.db.models import Q
  33. from django.http import Http404
  34. from django.utils.translation import ugettext as _
  35. from django.db.models.signals import pre_save
  36. from django.contrib.localflavor.us.models import PhoneNumberField
  37. from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
  38. from django.core.exceptions import FieldError
  39. from django.db import connection
  40. from common.models import AutoSlug, makeSlug
  41. import settings
  42. from django.core.urlresolvers import reverse
  43. from GChartWrapper import Pie3D
  44. from utils.charts import Votes, Accordance, get_percents
  45. def term_to_id(term):
  46. """
  47. Function term_to_id(term)
  48. Retrieves a primary key from term instance of TermOfOffice class.
  49. If term is integer function just returns the term value.
  50. Returns term's instance key
  51. """
  52. if type(term) == type(0):
  53. term = get_object_or_404(TermOfOffice, id = term)
  54. return term.id
  55. else:
  56. return term.id
  57. def person_to_id(person):
  58. """
  59. Function person_to_id(person)
  60. Retrieves a primary key from person instance of Person class.
  61. If person is integer function just returns the person value.
  62. Returns person's instance primary key
  63. """
  64. if type(person) == type(0):
  65. return person
  66. else:
  67. return person.id
  68. def group_to_id(group):
  69. """
  70. Function group_to_id(group)
  71. Retrieves a primary key from person instance of Group class.
  72. If group is integer function just returns the group value.
  73. Returns group's instance primary key
  74. """
  75. if type(group) == type(0):
  76. return group
  77. else:
  78. return group.id
  79. def poll_to_id(poll):
  80. """
  81. Function poll_to_id(poll)
  82. Retrieves a primary key from poll instance of Poll class.
  83. If poll is integer function just returns the poll value.
  84. Returns poll's instance primary key
  85. """
  86. if type(poll) == type(0):
  87. return poll
  88. else:
  89. return poll.id
  90. class TermOfOffice(AutoSlug):
  91. """
  92. Class TermOfOffice(AutoSlug)
  93. The TermOfOffice represents a period of parliament between two pools.
  94. There are several attributes:
  95. yearStart - the first electoral year (string)
  96. yearEnd - the last electoral year (string)
  97. slug - string (yearStart_yearEnd), autogenerated from
  98. yearStart, yearEnd
  99. termId - term ID assigned at psp.cz site
  100. """
  101. yearStart = models.CharField(_("Start Year"), max_length = 4)
  102. yearEnd = models.CharField(_("End Year"), max_length = 4)
  103. slug = models.SlugField(db_index = True, unique = True)
  104. termId = models.CharField(_("Term Id"), max_length = 4)
  105. slugs = {
  106. 'slug' : ('yearStart', 'yearEnd', ),
  107. }
  108. class Meta:
  109. """
  110. See http://docs.djangoproject.com/en/dev/ref/models/options/
  111. for details
  112. """
  113. #suppress class has no __init__ method
  114. #pylint: disable-msg=W0232
  115. #suppress too few public methods warning
  116. #pylint: disable-msg=R0903
  117. verbose_name = _("Term of Office")
  118. verbose_name_plural = _("Terms of Office")
  119. class Admin:
  120. """
  121. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  122. for details
  123. """
  124. #suppress class has no __init__ method
  125. #pylint: disable-msg=W0232
  126. #suppress too few public methods warning
  127. #pylint: disable-msg=R0903
  128. pass
  129. def isComplete(self):
  130. """
  131. Method isComplete()
  132. Method returns true if the given term instance is complete.
  133. The complete term instance has yearEnd attribute filled in. It
  134. actually means the given electoral term is over.
  135. Returns True if term is complete (over)
  136. """
  137. return self.yearEnd != ''
  138. def getStart(self):
  139. """
  140. Method getStart()
  141. Returns yearStart attribute as integer.
  142. Returns yearEnd attribute as integer.
  143. """
  144. return int(self.yearStart)
  145. def getEnd(self):
  146. """
  147. Method getEnd()
  148. Method returns yearEnd attribute as integer. If there is no
  149. yearEnd set yet, which happens for current terms, the method
  150. will return the current year instead.
  151. Returns yearEnd attribute as integer, if there is no yearEnd
  152. attribute set, function will return a current year.
  153. """
  154. if self.yearEnd == '':
  155. return datetime.datetime.today().year
  156. else:
  157. return int(self.yearEnd)
  158. def getStartDate(self):
  159. """
  160. Method getStartDate()
  161. We have to create faked start date of given term, it will be Jan 1 of
  162. the yearStart.
  163. Returns datetime.date(int(yearEnd), day = 1, month = 1)
  164. """
  165. retVal = datetime.date(
  166. day = 1,
  167. month = 1,
  168. year = self.getStart()
  169. )
  170. return retVal
  171. def getEndDate(self):
  172. """
  173. Method getEndDate()
  174. We have to create faked end date of given term, it will be Dec 31
  175. of the yearEnd.
  176. Returns datetime.date(int(yearStart), day = 31, month = 12)
  177. """
  178. retVal = datetime.date(
  179. day = 31,
  180. month = 12,
  181. year = self.getEnd()
  182. )
  183. return retVal
  184. def getRange(self):
  185. """
  186. Method getRange()
  187. Method returns a date range represeting given term instance
  188. as a tupple of two datetime.date instances, represeting
  189. (lowerBound, upperBound)
  190. Returns a tupple (self.getStartDate(), self.getEndDate())
  191. """
  192. return (self.getStartDate(), self.getEndDate(),)
  193. def getTermsList(self):
  194. """
  195. Method getTermsList()
  196. Returns list of terms rendered to .html tags. It's optimized for views
  197. which are using using ui-jquery .css. It renders links to terms, which
  198. are other than 'self'.
  199. Method is used in templates. It follows DRY principle, but violates
  200. MVC model - the presentation logic is implemented here.
  201. Returns list of strings with .html tags.
  202. """
  203. retVal = []
  204. #suppress 'no objects member' warning
  205. #pylint: disable-msg=E1101
  206. for t in TermOfOffice.objects.all():
  207. url = u''
  208. content = '%d - %d' % (t.getStart(), t.getEnd())
  209. if t == self:
  210. url = u'<span>%s</span>' % content
  211. url += u'<span class="ui-icon ui-icon-triangle-1-w"></span>'
  212. else:
  213. url = u'<a href="%s">%s</a>' % (
  214. reverse('term_tab',
  215. args = [
  216. str(t.getStart()),
  217. str(t.getEnd())
  218. ]
  219. ),
  220. content,
  221. )
  222. retVal.append(url)
  223. return retVal
  224. @staticmethod
  225. def get_term_or_404(start, end):
  226. """
  227. Static method get_term_or_404(start, end)
  228. Returns a TermOfOffice instance for given start, end year range. If
  229. there is no such term in DB, function (method) will raise Http404
  230. exception.
  231. The mandatory arguments start, end can be either strings or
  232. integer numbers:
  233. start - start year for term
  234. end - end year fo term
  235. Returns matching TermOfOffice instance or raises Http404
  236. """
  237. retVal = None
  238. if type(start) != type(''):
  239. start = str(start)
  240. if type(end) != type(''):
  241. end = str(end)
  242. try:
  243. #suppress 'no objects member' warning
  244. #pylint: disable-msg=E1101
  245. retVal = TermOfOffice.objects.get(yearStart = start, yearEnd = end)
  246. except ObjectDoesNotExist:
  247. if (int(start) <= int(end)):
  248. try:
  249. #suppress 'no objects member' warning
  250. #pylint: disable-msg=E1101
  251. retVal = TermOfOffice.objects.get(
  252. yearStart = start,
  253. yearEnd = ''
  254. )
  255. except ObjectDoesNotExist:
  256. raise Http404
  257. else:
  258. raise Http404
  259. return retVal
  260. @staticmethod
  261. def get_term_for_date(date):
  262. """
  263. Static method get_term_for_date(date)
  264. Returns the term for date. The mandatory argument date is a
  265. datetime.date object.
  266. Returns instance of TermOfOffice class.
  267. """
  268. #suppress 'no objects member' warning
  269. #pylint: disable-msg=E1101
  270. for t in TermOfOffice.objects.all().order_by('-id'):
  271. if date >= t.getStartDate() and date <= t.getEndDate():
  272. return t
  273. return None
  274. pre_save.connect(makeSlug, TermOfOffice)
  275. class Division(AutoSlug):
  276. """
  277. Class Division(AutoSlug)
  278. Division class represents an electoral area. The contains these attributes:
  279. term - link (ForeignKey) to term instance the particular division
  280. is bound to
  281. name - the division name (i.e. 'Karlovarsky kraj')
  282. icon - image icon (currently unused)
  283. slug - slug field (autogenerated from name)
  284. """
  285. term = models.ForeignKey(TermOfOffice)
  286. name = models.CharField(_("Division Name"), max_length = 20)
  287. icon = models.ImageField(_("Division Symbol"),
  288. upload_to = settings.SYMBOLS,
  289. blank = True
  290. )
  291. divId = models.CharField(_("Division Id"), max_length = 10)
  292. slug = models.SlugField(db_index = True, unique = True)
  293. slugs = {
  294. 'slug' : ('name', )
  295. }
  296. class Meta:
  297. """
  298. See http://docs.djangoproject.com/en/dev/ref/models/options/
  299. for details
  300. """
  301. #suppress class has no __init__ method
  302. #pylint: disable-msg=W0232
  303. #suppress too few public methods warning
  304. #pylint: disable-msg=R0903
  305. verbose_name = _("Division")
  306. verbose_name_plural = _("Divisions")
  307. unique_together = (('term', 'divId',),)
  308. def getName(self):
  309. """
  310. Method getName()
  311. Method strips 'Volebni kraj' string from data, which are stored in DB.
  312. Returns the division name.
  313. """
  314. pattern = re.compile(u'.*Volební\s*kraj(?P<name>.*)', re.U)
  315. match = pattern.match(self.name)
  316. retVal = u''
  317. if match:
  318. retVal += match.group('name')
  319. else:
  320. retVal = self.name
  321. return retVal.strip()
  322. pre_save.connect(makeSlug, Division)
  323. #suppress warning too many public methods
  324. #pylint: disable-msg=R0904
  325. class PersonManager(models.Manager):
  326. """
  327. Class PersonManager
  328. PersonManager provides predefined select to retrieve Person instances from DB.
  329. Depending on method used the Person instance might be extended by extra attributes
  330. (see method descriptions further down).
  331. The PersonManager is a default manager for Person class.
  332. """
  333. def createPersonFromRow(self, row):
  334. """
  335. Method createPersonFromRow
  336. Method creates an instance of Person class from row, fetched from DB.
  337. The row is list, which represents the row retrieved. The method expects
  338. row argument to contain at least 14 elements. The order of elements
  339. in array is as follows:
  340. 0 - integer representinf Person's instance primary key
  341. 1 - mpId string representing person's key at psp.cz
  342. 2 - name (unicode string)
  343. 3 - surname (unicode string)
  344. 4 - title prepended before first name (unicode string)
  345. 5 - title, which follows sutname (unicode string)
  346. 6 - birthDate (datetime.date instance)
  347. 7 - email (string)
  348. 8 - office address (string)
  349. 9 - regionOffice address (office located at MP's home town)
  350. 10 - MP's phone (string)
  351. 11 - slug, which is a parameter to construct link (string)
  352. 12 - homePage/blog link to blog (string)
  353. 13 - link to profile at http://nasipolitici.cz
  354. Returns instance of Person class
  355. """
  356. person = self.model(
  357. id = row[0], #primary Key
  358. mpId = row[1], #mpId
  359. name = row[2], #name
  360. surname = row[3], #surname
  361. title = row[4], #title
  362. titleLast = row[5], #titleLast
  363. birthDate = row[6], #birthDate
  364. email = row[7], #email
  365. office = row[8], #office
  366. regionOffice = row[9], #regionOffice
  367. phone = row[10], #phone number
  368. slug = row[11], # slug field
  369. homePage = row[12], # homePage/blogField
  370. nasiPoliticiUrl = row[13]
  371. )
  372. return person
  373. def commonSelect(self, where, orderBy, desc, term = None):
  374. """
  375. Method commonSelect(where, orderBy, desc, term = None)
  376. Retrieves a list of Person objects matching the query. The application
  377. should never use commonSelect() directly. It should use commonSelect()
  378. wrappers instead:
  379. getPersons(), getPersonsOrderByName(),getPersonsOrderByAge(),
  380. getPersonsOrderByDivision(), getPersonsOrderByParty(),
  381. getPersonsOrderByAbsences(), getPerson()
  382. All these methods are explained further in the text.
  383. The arguments are parts of SELECT command:
  384. where - is optional WHERE clause (it can be an empty string)
  385. order - by is order by statement
  386. desc - is boolean flag if true the order will be descendant
  387. term - optional argument, if no term is passed, all MPs are
  388. selected.
  389. Besides standard attribute the Person instance is extended by some
  390. more:
  391. detail - an instance of PersonDetail class
  392. divisionName - the name of electoral division the MP comes
  393. from
  394. partyName - the name of party the MP is member of
  395. votes - is an instance of Votes class. It wraps up the
  396. vote stats for given MP in term.
  397. terms - in case no term was specified, this attribute contains
  398. list of all terms the MP was member of parliament.
  399. Returns list of Person objects extended by few attributes (see above).
  400. """
  401. #suppress too many branches refactore warning
  402. #pylint: disable-msg=R0912
  403. retVal = []
  404. select = ''
  405. termId = None
  406. if orderBy != None and orderBy != '':
  407. if desc:
  408. orderBy += ' DESC'
  409. else:
  410. orderBy += ' ASC'
  411. if term != None:
  412. termId = term_to_id(term)
  413. select = u"""
  414. SELECT DISTINCT person.id,
  415. person.mpId,
  416. person.name,
  417. person.surname,
  418. person.title,
  419. person.titleLast,
  420. person.birthDate,
  421. person.email,
  422. person.office,
  423. person.regionOffice,
  424. person.phone,
  425. person.slug,
  426. person.homePage,
  427. person.nasiPoliticiUrl,
  428. detail.id,
  429. division.id,
  430. party.id,
  431. SUM(votes.absences) + SUM(votes.excused) AS absences,
  432. SUM(votes.absences) + SUM(votes.excused) +
  433. SUM(votes.votes_refrains) +
  434. SUM(votes.votes_aye) +
  435. SUM(votes.votes_nay) AS total,
  436. SUM(votes.votes_aye) AS votesAye,
  437. SUM(votes.votes_nay) AS votesNay,
  438. SUM(votes.votes_refrains) AS refrains,
  439. SUM(votes.absences) AS unexcAbs,
  440. SUM(votes.excused) AS excsdAbs
  441. FROM psp_person AS person
  442. INNER JOIN psp_group AS parlament ON parlament.type LIKE 'PARLAMENT' AND
  443. parlament.term_id = %d
  444. INNER JOIN psp_group AS party ON party.type LIKE 'KLUB' AND
  445. party.term_id = %d
  446. INNER JOIN psp_membership AS partymship ON
  447. partymship.group_id = party.id AND
  448. partymship.post LIKE '%%%%len' AND
  449. partymship.person_id = person.id
  450. INNER JOIN psp_membership AS parlmship ON
  451. parlmship.group_id = parlament.id AND
  452. parlmship.post LIKE 'poslan%%%%' AND
  453. parlmship.person_id = person.id
  454. INNER JOIN psp_persondetail AS detail ON
  455. detail.person_id = person.id AND
  456. detail.term_id = %d
  457. INNER JOIN psp_division AS division ON
  458. division.id = detail.region_id
  459. INNER JOIN psp_votestats AS votes ON
  460. votes.person_id = person.id AND
  461. votes.term_id = %d
  462. %s
  463. GROUP BY person.id %s;
  464. """
  465. select = select % (termId, termId, termId, termId, where, orderBy)
  466. else:
  467. select = u"""
  468. SELECT person.id,
  469. person.mpId,
  470. person.name,
  471. person.surname,
  472. person.title,
  473. person.titleLast,
  474. person.birthDate,
  475. person.email,
  476. person.office,
  477. person.regionOffice,
  478. person.phone,
  479. person.slug,
  480. person.homePage,
  481. person.nasiPoliticiUrl,
  482. detail.id,
  483. division.id,
  484. party.id,
  485. SUM(votes.absences) + SUM(votes.excused) AS absences,
  486. SUM(votes.absences) + SUM(votes.excused) +
  487. SUM(votes.votes_refrains) +
  488. SUM(votes.votes_aye) +
  489. SUM(votes.votes_nay) AS total,
  490. SUM(votes.votes_aye) AS votesAye,
  491. SUM(votes.votes_nay) AS votesNay,
  492. SUM(votes.votes_refrains) AS refrains,
  493. SUM(votes.absences) AS unexcAbs,
  494. SUM(votes.excused) AS excsdAbs
  495. FROM psp_person AS person
  496. INNER JOIN psp_group AS party ON party.type LIKE 'KLUB'
  497. INNER JOIN psp_membership AS partymship ON
  498. partymship.group_id = party.id AND
  499. partymship.post LIKE '_len' AND
  500. partymship.person_id = person.id
  501. INNER JOIN psp_persondetail AS detail ON
  502. detail.person_id = person.id
  503. INNER JOIN psp_division AS division ON
  504. division.id = detail.region_id
  505. INNER JOIN psp_votestats AS votes ON
  506. votes.person_id = person.id
  507. %s
  508. GROUP BY person.id %s;
  509. """
  510. select = select % (where, orderBy)
  511. cursor = connection.cursor()
  512. cursor.execute(select)
  513. for row in cursor.fetchall():
  514. mp = self.createPersonFromRow(row)
  515. # row[14] - missed polls
  516. # row[15] - total number of all polls
  517. mp.votes = Votes(
  518. vAye = row[19],
  519. vNay = row[20],
  520. vRefrains = row[21],
  521. absences = row[22],
  522. excused = row[23]
  523. )
  524. mp.detail = None
  525. #suppress 'Exception does not do anything' warning
  526. #pylint: disable-msg=W0704
  527. try:
  528. #suppress 'no objects member' warning
  529. #pylint: disable-msg=E1101
  530. mp.detail = PersonDetail.objects.get(id = row[14])
  531. except ObjectDoesNotExist:
  532. pass
  533. mp.divisionName = ''
  534. #suppress 'Exception does not do anything' warning
  535. #pylint: disable-msg=W0704
  536. try:
  537. # row[12] division ID
  538. #suppress 'no objects member' warning
  539. #pylint: disable-msg=E1101
  540. mp.divisionName = Division.objects.get(id = row[15]).getName()
  541. except ObjectDoesNotExist:
  542. pass
  543. mp.partyName = ''
  544. #suppress 'Exception does not do anything' warning
  545. #pylint: disable-msg=W0704
  546. try:
  547. # row[13] party club id
  548. #suppress 'no objects member' warning
  549. #pylint: disable-msg=E1101
  550. mp.partyGroup = Group.objects.get(id = row[16])
  551. mp.partyName = mp.partyGroup.getPartyName()
  552. except ObjectDoesNotExist:
  553. pass
  554. mp.terms = []
  555. #suppress 'no objects member' warning
  556. #pylint: disable-msg=E1101
  557. for mship in Membership.objects.filter(
  558. person = mp,
  559. group__type = 'PARLAMENT'
  560. ).order_by('group__term__id'):
  561. mp.terms.append(mship.group.term)
  562. mp.term = term
  563. if term != None:
  564. mp.terms.append(term)
  565. mp.age = term.getStart() - mp.birthDate.year
  566. else:
  567. try:
  568. t = mp.terms[len(mp.terms) - 1]
  569. mp.age = t.getStart() - mp.birthDate.year
  570. except IndexError:
  571. mp.age = 0
  572. retVal.append(mp)
  573. return retVal
  574. def getPersons(self, desc = False, term = None):
  575. """
  576. Method getPersons(desc = False, term = None)
  577. Method uses commonSelect() to retrieve list of persons. The persons are
  578. ordered by surname, name in ascendant order by default. If no term
  579. argument is used function retrieves list of all MPs, otherwise it
  580. fetches list of MPs for particular term only. All arguments are optional:
  581. desc - false on default (the MPs will be ordered in ascendant
  582. order by surname, name)
  583. term - if no term is specified then all MPs are retrieved,
  584. each MP will have extra attribute terms, which
  585. a list of all terms he/she was working as MP.
  586. Returns list of MPs ordered by surname, name
  587. """
  588. orderBy = 'ORDER BY person.surname, person.name'
  589. return self.commonSelect( where = '',
  590. orderBy = orderBy,
  591. desc = desc,
  592. term = term
  593. )
  594. def getPersonsOrderByName(self, desc = False, term = None):
  595. """
  596. Method getPersonsOrderByName(desc = False, term = None)
  597. Method uses commonSelect() to retrieve list of persons. The persons are
  598. ordered by name, surname in ascendant order by default. If no term
  599. argument is used function retrieves list of all MPs, otherwise it
  600. fetches list of MPs for particular term only. All arguments are optional:
  601. desc - false on default (the MPs will be ordered in ascendant
  602. order by surname, name)
  603. term - if no term is specified then all MPs are retrieved,
  604. each MP will have extra attribute terms, which
  605. a list of all terms he/she was working as MP.
  606. Returns list of MPs ordered by name, surname
  607. """
  608. orderBy = 'ORDER BY person.name, person.surname'
  609. return self.commonSelect( where = '',
  610. orderBy = orderBy,
  611. desc = desc,
  612. term = term
  613. )
  614. def getPersonsOrderByAge(self, desc = False, term = None):
  615. """
  616. Method getPersonsOrderByAge(desc = False, term = None)
  617. Uses commonSelect() to retreive list of person objects. Objects are
  618. ordered by age. The age is computed with respect to the begining of
  619. given term. If no term is given then the recent term the MP was in
  620. parliament is used. By default the MPs are ordered in ascendant
  621. order. The optional arguments are as follows:
  622. desc - default False, (descendant/ascendant) order
  623. term - if no term is specified then all MPs are retrieved,
  624. each MP will have extra attribute terms, which
  625. a list of all terms he/she was working as MP.
  626. Returns list of MPs ordered by age in ascendant order by default.
  627. """
  628. orderBy = 'ORDER BY person.birthDate, person.surname, person.name'
  629. retVal = self.commonSelect( where = '',
  630. orderBy = orderBy,
  631. desc = desc,
  632. term = term
  633. )
  634. #We need to sort by age here, age is computed with respect to the
  635. #start of parliementar membership.
  636. if desc:
  637. retVal.sort(lambda a, b: a.age - b.age)
  638. else:
  639. retVal.sort(lambda a, b: b.age - a.age)
  640. return retVal
  641. def getPersonsOrderByDivision(self, desc = False, term = None):
  642. """
  643. Method getPersonsOrderByAge(desc = False, term = None)
  644. Uses commonSelect() to retrieve list of MPs (person objects) ordered by
  645. division name. The defaut sort order is ascendant. If no term is
  646. specified MPs for all terms are retrieved. The optional arguments are:
  647. desc - default False, (descendant/ascendant) order
  648. term - if no term is specified then all MPs are retrieved,
  649. each MP will have extra attribute terms, which
  650. a list of all terms he/she was working as MP.
  651. Returns list of MPs ordered by division in ascendant order by default.
  652. """
  653. orderBy = 'ORDER BY division.name, person.surname, person.name'
  654. return self.commonSelect( where = '',
  655. orderBy = orderBy,
  656. desc = desc,
  657. term = term
  658. )
  659. def getPersonsOrderByParty(self, desc = False, term = None):
  660. """
  661. Method getPersonsOrderByParty(desc = False, term = None)
  662. Uses commonSelect() to retrieve the list of MPs (person objects) ordered
  663. by the name of party club they are member of. The default sort order
  664. is ascendant. If no term is specified MPs for all terms are retreived.
  665. The optional arguments are as follows:
  666. desc - default False, (descendant/ascendant) order
  667. term - if no term is specified then all MPs are retrieved,
  668. each MP will have extra attribute terms, which
  669. a list of all terms he/she was working as MP.
  670. Returns list of MPs ordered by their party name in ascendant order.
  671. """
  672. orderBy = 'ORDER BY party.name, person.surname, person.name'
  673. return self.commonSelect( where = '',
  674. orderBy = orderBy,
  675. desc = desc,
  676. term = term
  677. )
  678. def getPersonsOrderByAbsences(self, desc = False, term = None):
  679. """
  680. Method getPersonsOrderByAbsences(desc, term)
  681. Uses commonSelect() to retreive list of MPs (person objects) ordered
  682. by absences. The default sort order is ascendant (from the least
  683. absences to the most absences). If no term is specified MPs for all
  684. terms are retrieved. The optional arguments are as follows:
  685. desc - default False, (descendant/ascendant) order
  686. term - if no term is specified then all MPs are retrieved,
  687. each MP will have extra attribute terms, which
  688. a list of all terms he/she was working as MP.
  689. Returns list of MPs ordered by absences in ascendant order.
  690. """
  691. orderBy = 'ORDER BY absences'
  692. retVal = self.commonSelect( where = '',
  693. orderBy = orderBy,
  694. desc = desc,
  695. term = term
  696. )
  697. #we need to sort by computed absences, which are in relative values,
  698. #the DB select sorts by absolute values, which are misleading
  699. #we need to keep 2 decimal places presition, therefore we mult by 100
  700. if desc:
  701. retVal.sort(
  702. lambda a, b: int(a.votes.totalAbsencesPerc * 100) - \
  703. int(b.votes.totalAbsencesPerc * 100)
  704. )
  705. else:
  706. retVal.sort(
  707. lambda a, b: int(b.votes.totalAbsencesPerc * 100) - \
  708. int(a.votes.totalAbsencesPerc * 100)
  709. )
  710. return retVal
  711. def getPerson(self, person, term = None):
  712. """
  713. Method getPerson(person, term)
  714. Uses commonSelect() to retrieve a single MP (Person object). If no term
  715. is specified all data from all terms the particular person was member
  716. of parlament are retrieved. The mandatory person argument can be any of
  717. these below:
  718. integer - primary key for Person object
  719. string - slug defining MP
  720. person - instance of Person class
  721. If no term is specified the person will be extended by attribute terms,
  722. which is a list of all terms the person was active MP.
  723. Returns Person object or raises ObjectDoesNotExist exception, if no
  724. such MP can be found.
  725. """
  726. where = None
  727. if type(person) == type(0):
  728. where = 'WHERE person.id = %d' % person
  729. elif type(person) == type(u'') or type(person) == type(''):
  730. where = 'WHERE person.slug = "%s"' % person
  731. else:
  732. where = 'WHERE person.id = %d' % person.id
  733. #suppress 'redefining built-in list' warning
  734. #pylint: disable-msg=W0622
  735. list = self.commonSelect( where = where,
  736. orderBy = '',
  737. desc = False,
  738. term = term
  739. )
  740. if len(list) != 1:
  741. raise ObjectDoesNotExist
  742. return list[0]
  743. def getSlackers(self, dayStart, dayEnd = None, desc = True,
  744. colName = 'totalAbsences'):
  745. """
  746. Method getSlackers(dayStart, dayEnd = None, desc = True,
  747. colName = 'totalAbsences')
  748. Returns list of Person objects (MPs) ordered by their absences at
  749. polls for given date (date range). The only mandatory argument is
  750. dayStart, which defines desired day we want to retrieve list for.
  751. The rest of arguments is optional:
  752. dayEnd - if used, then dayStart, dayEnd range is defined
  753. desc - default value is true, which means the persons
  754. will be ordered in descendant order (from the
  755. biggest slackers toward the biggest hardworkers,
  756. with no absences)
  757. colName - the default value is 'totalAbsences' which means
  758. the MPs will be ordered by totalAbsences column.
  759. In fact you can use any column name you like:
  760. absences - unexcused absences
  761. excused - excused absences
  762. votesRefrain
  763. votesAye
  764. votesNay
  765. Returns list of MPs ordered by their absences.
  766. """
  767. select = None
  768. order = None
  769. if desc:
  770. order = 'DESC'
  771. else:
  772. order = 'ASC'
  773. if dayEnd == None:
  774. select = """
  775. SELECT id personId,
  776. mpId AS personMpId,
  777. name AS personName,
  778. surname AS personSurname,
  779. title AS personTitle,
  780. titleLast AS personTitleLast,
  781. birthDate AS personBirthDate,
  782. email AS personEmail,
  783. office AS personOffice,
  784. regionOffice AS personRegionOffice,
  785. phone AS personPhone,
  786. slug AS personSlug,
  787. homePage AS personHomePage,
  788. nasiPoliticiUrl AS personNasiPoliticiUrl,
  789. absences,
  790. excused,
  791. votesAye,
  792. votesNay,
  793. votesRefrain,
  794. (absences + excused) AS totalAbsences FROM (
  795. SELECT mp.id AS id,
  796. mp.mpId AS mpId,
  797. mp.name AS name,
  798. mp.surname AS surname,
  799. mp.title AS title,
  800. mp.titleLast AS titleLast,
  801. mp.birthDate AS birthDate,
  802. mp.email AS email,
  803. mp.office AS office,
  804. mp.regionOffice AS regionOffice,
  805. mp.phone AS phone,
  806. mp.slug AS slug,
  807. mp.homePage AS homePage,
  808. mp.nasiPoliticiUrl AS nasiPoliticiUrl,
  809. SUM(mpds.absences) AS absences,
  810. SUM(mpds.excused) AS excused,
  811. SUM(mpds.votes_aye) AS votesAye,
  812. SUM(mpds.votes_nay) AS votesNay,
  813. SUM(mpds.votes_refrain) AS votesRefrain
  814. FROM psp_mpdaystats AS mpds
  815. INNER JOIN psp_person AS mp ON
  816. mp.id = mpds.person_id
  817. WHERE mpds.date = '%d-%02d-%02d'
  818. GROUP BY id
  819. ) GROUP BY id ORDER BY %s %s;
  820. """
  821. select = select % ( dayStart.year, dayStart.month, dayStart.day,
  822. colName, order
  823. )
  824. else:
  825. select = """
  826. SELECT id personId,
  827. mpId AS personMpId,
  828. name AS personName,
  829. surname AS personSurname,
  830. title AS personTitle,
  831. titleLast AS personTitleLast,
  832. birthDate AS personBirthDate,
  833. email AS personEmail,
  834. office AS personOffice,
  835. regionOffice AS personRegionOffice,
  836. phone AS personPhone,
  837. slug AS personSlug,
  838. homePage AS personHomePage,
  839. nasiPoliticiUrl AS personNasiPoliticiUrl,
  840. absences,
  841. excused,
  842. votesAye,
  843. votesNay,
  844. votesRefrain,
  845. (absences + excused) AS totalAbsences FROM (
  846. SELECT mp.id AS id,
  847. mp.mpId AS mpId,
  848. mp.name AS name,
  849. mp.surname AS surname,
  850. mp.title AS title,
  851. mp.titleLast AS titleLast,
  852. mp.birthDate AS birthDate,
  853. mp.email AS email,
  854. mp.office AS office,
  855. mp.regionOffice AS regionOffice,
  856. mp.phone AS phone,
  857. mp.slug AS slug,
  858. mp.homePage AS homePage,
  859. mp.nasiPoliticiUrl AS nasiPoliticiUrl,
  860. SUM(mpds.absences) AS absences,
  861. SUM(mpds.excused) AS excused,
  862. SUM(mpds.votes_aye) AS votesAye,
  863. SUM(mpds.votes_nay) AS votesNay,
  864. SUM(mpds.votes_refrain) AS votesRefrain
  865. FROM psp_mpdaystats AS mpds
  866. INNER JOIN psp_person AS mp ON
  867. mp.id = mpds.person_id
  868. WHERE mpds.date
  869. BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d'
  870. GROUP BY id
  871. ) GROUP BY id ORDER BY %s %s;
  872. """
  873. select = select % ( dayStart.year, dayStart.month, dayStart.day,
  874. dayEnd.year, dayEnd.month, dayEnd.day,
  875. colName, order
  876. )
  877. cursor = connection.cursor()
  878. cursor.execute(select)
  879. retVal = []
  880. for row in cursor.fetchall():
  881. mp = self.createPersonFromRow(row)
  882. mp.votes = Votes(
  883. vAye = row[17],
  884. vNay = row[16],
  885. vRefrains = row[18],
  886. absences = row[14],
  887. excused = row[15],
  888. sDate = dayStart,
  889. eDate = dayEnd
  890. )
  891. retVal.append(mp)
  892. return retVal
  893. #suppress 'method could be a function' refactor warning
  894. #pylint: disable-msg=R0201
  895. def countPersonsForDay(self, date):
  896. """
  897. Method countPersonsForDay(date)
  898. Requires date argument, which is an instance of datetime.date object.
  899. It defines the day we want to count all MPs. The method is used to
  900. render charts.
  901. Returns the number of MPs for particular day.
  902. """
  903. select = """
  904. SELECT DISTINCT person.id
  905. FROM psp_person AS person
  906. INNER JOIN psp_group AS party ON party.type LIKE 'KLUB'
  907. INNER JOIN psp_membership AS partymship ON
  908. partymship.group_id = party.id AND
  909. partymship.post LIKE 'člen' AND
  910. partymship.person_id = person.id AND
  911. ((partymship.end IS NULL AND
  912. partymship.start <= '%d-%02d-%02d'
  913. ) OR (
  914. '%d-%02d-%02d' BETWEEN partymship.start AND
  915. partymship.end
  916. ))
  917. GROUP BY person.id;
  918. """
  919. select = select % ( date.year, date.month, date.day,
  920. date.year, date.month, date.day
  921. )
  922. cursor = connection.cursor()
  923. cursor.execute(select)
  924. return len(cursor.fetchall())
  925. def getRebels(self, dayStart, dayEnd = None, desc = True, colName = 'same'):
  926. """
  927. Method getRebels(dayStart, dayEnd = None, desc = True, colName = 'same')
  928. Retrieves the list of MPs ordered by poll divergence. Rebels are MPs
  929. who are voting against majority of their own party club. The higher
  930. divergence in poll results between particular MP and his/her club the
  931. bigger rebel MP is. The only mandatory argument is dayStart, which is
  932. an instance of datetime.date class. The other arguments are optional:
  933. dayEnd - if defined then the list of MPs is retrieved for
  934. day range <dayStart, dayEnd>
  935. orderDesc - by default the rebels (those who diverge most)
  936. will be at the begining of the list.
  937. colName - by default we are ordering by 'same' column, other
  938. possible (reasonable values are):
  939. activeSame
  940. Same vs. Active - in case of accordance of MP's we need to distinguish
  941. between rebels and active rebels. Rebels are all MP's whose results
  942. differ with majority vote of theri party club including absences - MP's
  943. absences are also included into calculation. The activeSame column,
  944. ignore does not include polls, where particular MP was not present,
  945. only polls, where given MP was actively participating in, these will
  946. be included, thus we call them active - actively voting against
  947. majority of party club.
  948. Returns list of MPs ordered by result divergence between them and their
  949. party club.
  950. """
  951. select = None
  952. order = None
  953. if desc:
  954. order = 'DESC'
  955. else:
  956. order = 'ASC'
  957. activeParm = '\n'
  958. if dayEnd != None:
  959. select = """
  960. SELECT
  961. person.id,
  962. person.mpId,
  963. person.name,
  964. person.surname,
  965. person.title,
  966. person.titleLast,
  967. person.birthDate,
  968. person.email,
  969. person.office,
  970. person.regionOffice,
  971. person.phone,
  972. person.slug,
  973. person.homePage,
  974. person.nasiPoliticiUrl,
  975. pgroup.id AS id,
  976. SUM( CASE
  977. WHEN gstats.result = mpvote.result THEN 1
  978. ELSE 0
  979. END
  980. ) AS same,
  981. SUM(1) AS total,
  982. SUM( CASE
  983. WHEN mpvote.result IN ('A', 'N', 'Z') THEN
  984. CASE
  985. WHEN gstats.result = mpvote.result THEN 1
  986. ELSE 0
  987. END
  988. ELSE 0
  989. END
  990. ) AS activeSame,
  991. SUM( CASE
  992. WHEN mpvote.result IN ('A', 'N', 'Z') THEN 1
  993. ELSE 0
  994. END
  995. ) AS activeTotal
  996. FROM psp_person AS person,
  997. psp_membership AS mship,
  998. psp_group AS pgroup,
  999. psp_groupstats AS gstats,
  1000. psp_mpvote AS mpvote,
  1001. psp_poll AS poll
  1002. WHERE pgroup.type LIKE 'KLUB' AND
  1003. mship.group_id = pgroup.id AND
  1004. mship.person_id = person.id AND
  1005. mship.post LIKE '%%%%len' AND
  1006. --we will handle lower bound first
  1007. (('%d-%02d-%02d' BETWEEN mship.start AND mship.end) OR
  1008. ('%d-%02d-%02d' > mship.start AND mship.end IS NULL)) AND
  1009. --we will handle upper bound next
  1010. (('%d-%02d-%02d' BETWEEN mship.start AND mship.end) OR
  1011. ('%d-%02d-%02d' > mship.start AND mship.end IS NULL)) AND
  1012. mpvote.poll_id = poll.id AND mpvote.person_id = person.id AND %s
  1013. gstats.poll_id = poll.id AND gstats.group_id = pgroup.id AND
  1014. poll.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d'
  1015. GROUP BY person.id
  1016. ORDER BY %s %s;
  1017. """
  1018. select = select % ( dayStart.year, dayStart.month, dayStart.day,
  1019. dayStart.year, dayStart.month, dayStart.day,
  1020. dayEnd.year, dayEnd.month, dayEnd.day,
  1021. dayEnd.year, dayEnd.month, dayEnd.day,
  1022. activeParm,
  1023. dayStart.year, dayStart.month, dayStart.day,
  1024. dayEnd.year, dayEnd.month, dayEnd.day,
  1025. colName, order
  1026. )
  1027. else:
  1028. select = """
  1029. SELECT
  1030. person.id,
  1031. person.mpId,
  1032. person.name,
  1033. person.surname,
  1034. person.title,
  1035. person.titleLast,
  1036. person.birthDate,
  1037. person.email,
  1038. person.office,
  1039. person.regionOffice,
  1040. person.phone,
  1041. person.slug,
  1042. person.homePage,
  1043. person.nasiPoliticiUrl,
  1044. pgroup.id,
  1045. SUM( CASE
  1046. WHEN gstats.result = mpvote.result THEN 1
  1047. ELSE 0
  1048. END
  1049. ) AS same,
  1050. SUM(1) AS total,
  1051. SUM( CASE
  1052. WHEN mpvote.result IN ('A', 'N', 'Z') THEN
  1053. CASE
  1054. WHEN gstats.result = mpvote.result THEN 1
  1055. ELSE 0
  1056. END
  1057. ELSE 0
  1058. END
  1059. ) AS activeSame,
  1060. SUM( CASE
  1061. WHEN mpvote.result IN ('A', 'N', 'Z') THEN 1
  1062. ELSE 0
  1063. END
  1064. ) AS activeTotal
  1065. FROM psp_person AS person,
  1066. psp_membership AS mship,
  1067. psp_group AS pgroup,
  1068. psp_groupstats AS gstats,
  1069. psp_mpvote AS mpvote,
  1070. psp_poll AS poll
  1071. WHERE pgroup.type LIKE 'KLUB' AND
  1072. mship.group_id = pgroup.id AND
  1073. mship.person_id = person.id AND
  1074. mship.post LIKE '%%%%len' AND
  1075. (('%d-%02d-%02d' BETWEEN mship.start AND mship.end) OR
  1076. ('%d-%02d-%02d' > mship.start AND mship.end IS NULL)) AND
  1077. mpvote.poll_id = poll.id AND mpvote.person_id = person.id AND %s
  1078. gstats.poll_id = poll.id AND gstats.group_id = pgroup.id AND
  1079. poll.date = '%d-%02d-%02d'
  1080. GROUP BY person.id
  1081. ORDER BY %s %s;
  1082. """
  1083. select = select % ( dayStart.year, dayStart.month, dayStart.day,
  1084. dayStart.year, dayStart.month, dayStart.day,
  1085. activeParm,
  1086. dayStart.year, dayStart.month, dayStart.day,
  1087. colName, order
  1088. )
  1089. cursor = connection.cursor()
  1090. cursor.execute(select)
  1091. retVal = []
  1092. for row in cursor.fetchall():
  1093. mp = self.createPersonFromRow(row)
  1094. mp.party = Group.objects.get(id = row[14])
  1095. mp.accordance = Accordance(
  1096. same = row[15],
  1097. diff = row[16] - row[15],
  1098. sDate = dayStart,
  1099. eDate = dayEnd
  1100. )
  1101. #if MP was not present at all, then pretend
  1102. #100% activeAccordance
  1103. if row[17] == 0 and row[18] == 0:
  1104. mp.activeAccordance = Accordance(
  1105. same = 10,
  1106. diff = 0,
  1107. sDate = dayStart,
  1108. eDate = dayEnd
  1109. )
  1110. else:
  1111. mp.activeAccordance = Accordance(
  1112. same = row[17],
  1113. diff = row[18] - row[17],
  1114. sDate = dayStart,
  1115. eDate = dayEnd
  1116. )
  1117. retVal.append(mp)
  1118. return retVal
  1119. class Person(AutoSlug):
  1120. """
  1121. Class Person(AutoSlug)
  1122. Person instances represent MPs. The default manager for class is
  1123. PersonManager. There are just few attributes fetched from
  1124. person table in DB:
  1125. mpId - MP's ID (primary key) at psp.cz
  1126. name - MP's name
  1127. surname - MP's surname
  1128. title - title in front of name
  1129. titleLast - title after the person's name
  1130. birthDate - birth date
  1131. email
  1132. homePage - URLField with link to MP's blog/homepage
  1133. office - address to office
  1134. regionOffice - region office address
  1135. phone,
  1136. slug - slug field is composed of name and surname on instance save
  1137. i.e. josef_novak
  1138. nasiPoliticiUrl - link to MP's profile at www.nasipolitici.cz
  1139. The rest of attributes are volatile - they are computed by select and
  1140. added to instance.
  1141. """
  1142. mpId = models.CharField(
  1143. _("MP ID"), unique = True, max_length = 10, db_index = True
  1144. )
  1145. name = models.CharField(_("Name"), max_length = 30)
  1146. surname = models.CharField(_("Surname"), max_length = 30)
  1147. title = models.CharField(_("Title"), max_length = 10, blank = True)
  1148. titleLast = models.CharField(
  1149. _("Title after Name"), max_length = 10, blank = True
  1150. )
  1151. birthDate = models.DateField(_("Birthdate"))
  1152. email = models.EmailField(_("Email"))
  1153. homePage = models.URLField(
  1154. _("Homepage"), blank = True, verify_exists = False
  1155. )
  1156. office = models.CharField(_("Office"), max_length = 40)
  1157. regionOffice = models.CharField(
  1158. _("Electoral Ward Office"), max_length = 60
  1159. )
  1160. phone = PhoneNumberField(_("Phone Number"))
  1161. slug = models.SlugField(db_index = True, unique = True)
  1162. nasiPoliticiUrl = models.CharField(
  1163. _("Link to NasiPolitici site"), max_length = 120, blank = True
  1164. )
  1165. slugs = {
  1166. 'slug' : ('name', 'surname', 'title', 'titleLast')
  1167. }
  1168. votes = None
  1169. objects = PersonManager()
  1170. class Meta:
  1171. """
  1172. See http://docs.djangoproject.com/en/dev/ref/models/options/
  1173. for details
  1174. """
  1175. #suppress class has no __init__ method
  1176. #pylint: disable-msg=W0232
  1177. #suppress too few public methods warning
  1178. #pylint: disable-msg=R0903
  1179. verbose_name = _("MP")
  1180. verbose_name_plural = _("MPs")
  1181. class Admin:
  1182. """
  1183. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  1184. for details
  1185. """
  1186. #suppress class has no __init__ method
  1187. #pylint: disable-msg=W0232
  1188. #suppress too few public methods warning
  1189. #pylint: disable-msg=R0903
  1190. pass
  1191. @property
  1192. def sex(self):
  1193. """
  1194. Property sex
  1195. There are two values possible:
  1196. 'F' - for woman
  1197. 'M' - for man
  1198. The value is derrived from name.
  1199. """
  1200. retVal = ''
  1201. #suppress 'no endswith member' warning
  1202. #pylint: disable-msg=E1101
  1203. try:
  1204. retVal = self._sex
  1205. except AttributeError:
  1206. #suppress warning 'Attribute _sex defined outside of init'
  1207. #pylint: disable-msg=W0201
  1208. if self.name.endswith('a') and \
  1209. not self.surname.endswith(u'ý'):
  1210. #suppress warning 'Attribute _sex defined outside of init'
  1211. #pylint: disable-msg=W0201
  1212. self._sex = 'F'
  1213. else:
  1214. self._sex = 'M'
  1215. retVal = self._sex
  1216. return retVal
  1217. def getTermLinks(self, currentTerm = None):
  1218. """
  1219. Method getTermLinks(currentTerm = None)
  1220. Method renders a html elements with links, to MP's profiles for each term.
  1221. If optional parameter currentTerm is used then the matching term will
  1222. be just listed, no link will be generated for it.
  1223. Returns list of html elements - links to terms
  1224. """
  1225. retVal = []
  1226. url = u''
  1227. for parlMship in self.getParlMemberships():
  1228. url = u''
  1229. term = parlMship.group.term
  1230. content = '%d - %d' % (term.getStart(), term.getEnd(),)
  1231. if currentTerm != None and term == currentTerm:
  1232. url = u'<span>%s</span>' % content
  1233. url += u'<span class="ui-icon ui-icon-triangle-1-w"></span>'
  1234. else:
  1235. url = u'<a href="%s">%s</a>' % (
  1236. reverse('show_mp_term', args = [
  1237. self.slug,
  1238. str(term.getStart()),
  1239. str(term.getEnd())
  1240. ]),
  1241. content
  1242. )
  1243. retVal.append(url)
  1244. return retVal
  1245. def getParlMemberships(self):
  1246. """
  1247. Method getParlMemberships()
  1248. Returns a django.query.queryset instance with all MP's memberships in
  1249. group type 'PARLAMENT'. This is the easiest way to find out all terms
  1250. the given MP was working in PARLAMENT.
  1251. Returns a queryset
  1252. """
  1253. #suppress 'no membership_set member' warning
  1254. #pylint: disable-msg=E1101
  1255. retVal = self.membership_set.filter(
  1256. group__type__iexact = 'PARLAMENT',
  1257. post__iregex = '.*POSLAN.*'
  1258. )
  1259. return retVal
  1260. def getGroups(self, term):
  1261. """
  1262. Method getGroups(term)
  1263. Method returns a django.queryset instance, which retrieves all
  1264. memberships except the membership in group 'PARLAMENT' for given MP in
  1265. chosen term. The term is mandatory parameter.
  1266. Returns a queryset
  1267. """
  1268. retVal = []
  1269. #suppress 'no membership_set member' warning
  1270. #pylint: disable-msg=E1101
  1271. if term != None:
  1272. retVal = self.membership_set.filter(
  1273. ~Q(group__type__iexact = 'PARLAMENT'),
  1274. group__term = term,
  1275. post__iregex = u'.len'
  1276. )
  1277. else:
  1278. retVal = self.membership_set.filter(
  1279. ~Q(group__type__iexact = 'PARLAMENT'),
  1280. post__iregex = u'.len'
  1281. )
  1282. return retVal
  1283. def getMshipsForDateRange(self, dateStart, dateEnd = None):
  1284. """
  1285. Method getMshipsForDateRange(dateStart, dateEnd = None)
  1286. Method returns django.query.queryset instance, which will retrieve all
  1287. MP's membership in groups except group PARLAMENT in given date range
  1288. defined by <dateStart, dateEnd>. Both arguments are instances of
  1289. datetime.date class. If no dateEnd is specified method will return MP's
  1290. membership for given date specified by dateStart.
  1291. Returns a django.query.queryset instance
  1292. """
  1293. retVal = None
  1294. term = TermOfOffice.get_term_for_date(dateStart)
  1295. if dateEnd == None:
  1296. #suppress 'no membership_set member' warning
  1297. #pylint: disable-msg=E1101
  1298. try:
  1299. retVal = self.membership_set.filter(
  1300. ~Q(group__type__iexact = 'PARLAMENT'),
  1301. Q(start__lte = dateStart, end__gte = dateStart)|
  1302. Q(start__lte = dateStart, end = None),
  1303. group__term = term
  1304. )
  1305. except FieldError:
  1306. retVal = self.membership_set.filter(
  1307. ~Q(group__type__iexact = 'PARLAMENT'),
  1308. start__lte = dateStart,
  1309. end = None,
  1310. group__term = term
  1311. )
  1312. else:
  1313. try:
  1314. #suppress 'no membership_set member' warning
  1315. retVal = self.membership_set.filter( #pylint: disable-msg=E1101
  1316. ~Q(group__type__iexact = 'PARLAMENT'),
  1317. Q(start__lt = dateEnd) |
  1318. Q(start__lte = dateStart, end__gte = dateEnd) |
  1319. Q(end__range = (dateStart, dateEnd)),
  1320. group__term = term
  1321. )
  1322. except FieldError:
  1323. #suppress 'no membership_set member' warning
  1324. retVal = self.membership_set.filter( #pylint: disable-msg=E1101
  1325. ~Q(group__type__iexact = 'PARLAMENT'),
  1326. Q(start__lt = dateEnd) |
  1327. Q(start__lte = dateStart, end = None),
  1328. group__term = term
  1329. )
  1330. return retVal
  1331. def getPartyClub(self, date):
  1332. """
  1333. Method getPartyClub(date)
  1334. Method returns the group instance of type 'KLUB' (which is a party club
  1335. the MP is member of) for single date. The date is mandatory parameter -
  1336. the instance of datetime.date class.
  1337. Returns group instance of type 'KLUB'
  1338. """
  1339. t = TermOfOffice.get_term_for_date(date)
  1340. retVal = None
  1341. try:
  1342. #suppress 'no membership_set member' warning
  1343. #pylint: disable-msg=E1101
  1344. partyMship = self.membership_set.get(
  1345. group__type__iexact = 'KLUB',
  1346. group__term = t,
  1347. post__iregex = u'.len',
  1348. start__lte = date,
  1349. end__gte = date
  1350. )
  1351. retVal = partyMship.group
  1352. except ObjectDoesNotExist:
  1353. #suppress 'no membership_set member' warning
  1354. try:
  1355. partyMship = self.membership_set.get(#pylint: disable-msg=E1101
  1356. group__type__iexact = 'KLUB',
  1357. group__term = t,
  1358. post__iregex = u'.len',
  1359. end = None
  1360. )
  1361. retVal = partyMship.group
  1362. except MultipleObjectsReturned:
  1363. #suppress 'no membership_set member' warning
  1364. partyMship = \
  1365. self.membership_set.filter(#pylint: disable-msg=E1101
  1366. group__type__iexact = 'KLUB',
  1367. group__term = t,
  1368. post__iregex = u'.len',
  1369. end = None
  1370. ).order_by('-start')
  1371. retVal = partyMship[0].group
  1372. except MultipleObjectsReturned:
  1373. partyMship = self.membership_set.filter(
  1374. group__type__iexact = 'KLUB',
  1375. group__term = t,
  1376. post__iregex = u'.len',
  1377. start__lte = date,
  1378. end__gte = date
  1379. )
  1380. retVal = partyMship[0].group
  1381. return retVal
  1382. def getPartyMships(self, term):
  1383. """
  1384. Method getPartyMships(term)
  1385. Method retrieves all memberships in party clubs for given MP in chosen term.
  1386. The term is mandatory argument - instance of TermOfOffice class.
  1387. Returns a django.query.queryset instance
  1388. """
  1389. #suppress 'no membership_set member' warning
  1390. #pylint: disable-msg=E1101
  1391. retVal = self.membership_set.filter(
  1392. group__term = term,
  1393. group__type__iexact = 'KLUB'
  1394. ).order_by('start')
  1395. return retVal
  1396. def getAccordanceCharts(self, group, dateStart, dateEnd = None):
  1397. """
  1398. Method getAccordanceCharts(group, dateStart, dateEnd = None)
  1399. Returns a tupple, which consists of two pieCharts describing MP's
  1400. accordances with group. The first tupple member describes accordance
  1401. with group - the date include all meetings in given period. The second
  1402. chart in tupple does not cover polls, where particular MP was not
  1403. present. The arguments are as follows:
  1404. group - the group (either instance or id) we want to get
  1405. accordance chart for
  1406. dateStart - instance of datetime.date class
  1407. dateEnd - optional argument in case we want to
  1408. get accodance charts for period
  1409. <dateStart, dateEnd>
  1410. Returns tupple (accordanceChart, activeAccordanceChart)
  1411. """
  1412. mp = self.getAccordanceWithGroup(
  1413. group = group,
  1414. dateStart = dateStart,
  1415. dateEnd = dateEnd
  1416. )
  1417. if mp == None:
  1418. return (None, None,)
  1419. groupAccordance = mp.accordance.getChart()
  1420. title = None
  1421. if group.type == 'KLUB':
  1422. title = _('%s. %s shoda s %s') % (
  1423. mp.name[0], mp.surname, group.getPartyName()
  1424. )
  1425. else:
  1426. title = _('%s. %s shoda s %s') % (
  1427. mp.name[0], mp.surname, group.name
  1428. )
  1429. groupAccordance.title(title)
  1430. groupAccordance.size(330, 80)
  1431. activeGroupAccordance = mp.activeAccordance.getChart()
  1432. title = None
  1433. if group.type == 'KLUB':
  1434. title = _('%s. %s - aktivní shoda s %s') % (
  1435. mp.name[0], mp.surname, group.getPartyName()
  1436. )
  1437. else:
  1438. title = _('%s. %s - acktiví shoda s %s') % (
  1439. mp.name[0], mp.surname, group.name
  1440. )
  1441. activeGroupAccordance.title(title)
  1442. activeGroupAccordance.size(330, 80)
  1443. return (groupAccordance, activeGroupAccordance,)
  1444. def getAccordanceWithGroup(self, group, dateStart = None, dateEnd = None):
  1445. """
  1446. Method getAccordanceWithGroup(group, dateStart, dateEnd = None)
  1447. Retrieves a poll accordance data for MP with group. Person object is
  1448. extended by two 'accordance' attributes. Accordance attribute is the
  1449. instance of Accordance class. The arguments are as follows:
  1450. group - group to compute accordance with
  1451. dayStart - date object defining start day
  1452. dayEnd - date object defining end day, it is optional
  1453. parameter, if no dayEnd is specified, then accordance
  1454. for desired dayStart date is retreived only.
  1455. The new two attributes added to self instance are:
  1456. accordance - MP's poll accordance with group
  1457. activeAccordance - MP's poll accordance with group (only
  1458. polls, where MP is present are counted)
  1459. Returns self extended by two new attributes - accordance and
  1460. activeAccordance. Both attribute are instances of Accordance class.
  1461. """
  1462. groupId = group_to_id(group)
  1463. select = None
  1464. if dateEnd == None and dateStart == None:
  1465. try:
  1466. dateStart = Membership.objects.filter(
  1467. group__id = groupId
  1468. ).order_by('start')[0].start
  1469. dateEnd = Membership.objects.filter(
  1470. group__id = groupId,
  1471. end = None
  1472. )
  1473. if len(dateEnd) == 0:
  1474. dateEnd = Membership.objects.filter(
  1475. group__id = groupId
  1476. ).order_by('-end')[0].end
  1477. else:
  1478. dateEnd = datetime.date.today()
  1479. except IndexError:
  1480. return None
  1481. if dateEnd != None:
  1482. select = """
  1483. SELECT SUM(
  1484. CASE
  1485. WHEN gs.result <> vote.result THEN 1
  1486. ELSE 0
  1487. END
  1488. ) AS different,
  1489. SUM(
  1490. CASE
  1491. WHEN gs.result == vote.result THEN 1
  1492. ELSE 0
  1493. END
  1494. ) AS same,
  1495. SUM(
  1496. CASE
  1497. WHEN vote.result IN ('A', 'N', 'Z') AND
  1498. gs.result != vote.result THEN 1
  1499. ELSE 0
  1500. END
  1501. ) AS activeDifferent,
  1502. SUM(
  1503. CASE
  1504. WHEN vote.result IN ('A', 'N', 'Z') AND
  1505. gs.result == vote.result THEN 1
  1506. ELSE 0
  1507. END
  1508. ) AS activeSame
  1509. FROM psp_poll AS poll
  1510. INNER JOIN psp_mpvote AS vote ON
  1511. poll.id = vote.poll_id AND
  1512. vote.person_id = %d
  1513. INNER JOIN psp_groupstats AS gs ON
  1514. poll.id = gs.poll_id AND
  1515. gs.group_id = %d
  1516. WHERE poll.date BETWEEN '%d-%02d-%02d' AND
  1517. '%d-%02d-%02d';
  1518. """
  1519. #suppressing 'Person has no attribute id' warning
  1520. select = select % ( self.id,#pylint: disable-msg=E1101
  1521. groupId,
  1522. dateStart.year, dateStart.month, dateStart.day,
  1523. dateEnd.year, dateEnd.month, dateEnd.day,
  1524. )
  1525. else:
  1526. select = """
  1527. SELECT SUM(
  1528. CASE
  1529. WHEN gs.result <> vote.result THEN 1
  1530. ELSE 0
  1531. END
  1532. ) AS different,
  1533. SUM(
  1534. CASE
  1535. WHEN gs.result == vote.result THEN 1
  1536. ELSE 0
  1537. END
  1538. ) AS same,
  1539. SUM(
  1540. CASE
  1541. WHEN vote.result IN ('A', 'N', 'Z') AND
  1542. gs.result != vote.result THEN 1
  1543. ELSE 0
  1544. END
  1545. ) AS activeDifferent,
  1546. SUM(
  1547. CASE
  1548. WHEN vote.result IN ('A', 'N', 'Z') AND
  1549. gs.result == vote.result THEN 1
  1550. ELSE 0
  1551. END
  1552. ) AS activeSame
  1553. FROM psp_poll AS poll
  1554. INNER JOIN psp_mpvote AS vote ON
  1555. poll.id = vote.poll_id AND
  1556. vote.person_id = %d
  1557. INNER JOIN psp_groupstats AS gs ON
  1558. poll.id = gs.poll_id AND
  1559. gs.group_id = %d
  1560. WHERE poll.date = '%d-%02d-%02d';
  1561. """
  1562. #suppressing 'Person has no attribute id' warning
  1563. select = select % ( self.id,#pylint: disable-msg=E1101
  1564. groupId,
  1565. dateStart.year, dateStart.month, dateStart.day,
  1566. )
  1567. cursor = connection.cursor()
  1568. cursor.execute(select)
  1569. rows = cursor.fetchall()
  1570. if len(rows) != 1:
  1571. return None
  1572. row = rows[0]
  1573. #suppress warning 'Attribute _sex defined outside of init'
  1574. #pylint: disable-msg=W0201
  1575. self.accordance = Accordance(
  1576. diff = row[0],
  1577. same = row[1],
  1578. sDate = dateStart,
  1579. eDate = dateEnd
  1580. )
  1581. #if MP was not present at all, then pretend
  1582. #100% activeAccordance
  1583. if row[2] == 0 and row[3] == 0:
  1584. #suppress warning 'Attribute _sex defined outside of init'
  1585. #pylint: disable-msg=W0201
  1586. self.activeAccordance = Accordance(
  1587. diff = 0,
  1588. same = 10,
  1589. sDate = dateStart,
  1590. eDate = dateEnd
  1591. )
  1592. else:
  1593. #suppress warning 'Attribute _sex defined outside of init'
  1594. #pylint: disable-msg=W0201
  1595. self.activeAccordance = Accordance(
  1596. diff = row[2],
  1597. same = row[3],
  1598. sDate = dateStart,
  1599. eDate = dateEnd
  1600. )
  1601. return self
  1602. def getPersonWithStats(self, dayStart, dayEnd = None):
  1603. """
  1604. Method getPersonWithStats(dayStart, dateEnd = None)
  1605. Retrives statistics for person. The person will be extended by
  1606. attribute votes, which is instance of Votes class. The arguments
  1607. are as follows:
  1608. dayStart - instance of datetime.date class with desired
  1609. day we want to gather stats for
  1610. dayEnd - optional, if specified then <dateStart, dateEnd>
  1611. range is used instead of single day.
  1612. Returns a self object extended by votes attribute (the instance
  1613. of Votes class).
  1614. """
  1615. #save cycles in case the votes attribute already exists
  1616. if self.votes != None and\
  1617. self.votes.startDate == dayStart and self.votes.endDate == dayEnd:
  1618. return self
  1619. if dayEnd == None:
  1620. select = """
  1621. SELECT SUM(
  1622. CASE
  1623. WHEN vote.result = 'M' THEN 1
  1624. ELSE 0
  1625. END
  1626. ) AS excs,
  1627. SUM(
  1628. CASE
  1629. WHEN vote.result = '0' OR
  1630. vote.result = 'X' THEN 1
  1631. ELSE 0
  1632. END
  1633. ) AS abs,
  1634. SUM(
  1635. CASE
  1636. WHEN vote.result = 'N' THEN 1
  1637. ELSE 0
  1638. END
  1639. ) AS nay,
  1640. SUM (
  1641. CASE
  1642. WHEN vote.result = 'Z' THEN 1
  1643. ELSE 0
  1644. END
  1645. ) AS refrains,
  1646. SUM (
  1647. CASE
  1648. WHEN vote.result = 'A' THEN 1
  1649. ELSE 0
  1650. END
  1651. ) AS aye
  1652. FROM psp_poll AS poll
  1653. INNER JOIN psp_mpvote AS vote ON
  1654. poll.id = vote.poll_id AND
  1655. vote.person_id = %d
  1656. WHERE poll.date = '%d-%02d-%02d';
  1657. """
  1658. select = select % (
  1659. #suppressing 'Person has no attribute id' warning
  1660. self.id,#pylint: disable-msg=E1101
  1661. dayStart.year, dayStart.month, dayStart.day
  1662. )
  1663. else:
  1664. select = """
  1665. SELECT SUM(
  1666. CASE
  1667. WHEN vote.result = 'M' THEN 1
  1668. ELSE 0
  1669. END
  1670. ) AS excs,
  1671. SUM(
  1672. CASE
  1673. WHEN vote.result = '0' OR
  1674. vote.result = 'X' THEN 1
  1675. ELSE 0
  1676. END
  1677. ) AS abs,
  1678. SUM(
  1679. CASE
  1680. WHEN vote.result = 'N' THEN 1
  1681. ELSE 0
  1682. END
  1683. ) AS nay,
  1684. SUM (
  1685. CASE
  1686. WHEN vote.result = 'Z' THEN 1
  1687. ELSE 0
  1688. END
  1689. ) AS refrains,
  1690. SUM (
  1691. CASE
  1692. WHEN vote.result = 'A' THEN 1
  1693. ELSE 0
  1694. END
  1695. ) AS aye
  1696. FROM psp_poll AS poll
  1697. INNER JOIN psp_mpvote AS vote ON
  1698. poll.id = vote.poll_id AND
  1699. vote.person_id = %d
  1700. WHERE poll.date BETWEEN '%d-%02d-%02d' AND
  1701. '%d-%02d-%02d';
  1702. """
  1703. select = select % (
  1704. #suppressing 'Person has no attribute id' warning
  1705. self.id,#pylint: disable-msg=E1101
  1706. dayStart.year, dayStart.month, dayStart.day,
  1707. dayEnd.year, dayEnd.month, dayEnd.day
  1708. )
  1709. cursor = connection.cursor()
  1710. cursor.execute(select)
  1711. rows = cursor.fetchall()
  1712. if len(rows) != 1:
  1713. return None
  1714. row = rows[0]
  1715. retVal = Person(self)
  1716. retVal.votes = Votes(
  1717. excused = row[0],
  1718. absences = row[1],
  1719. vNay = row[2],
  1720. vRefrains = row[3],
  1721. vAye = row[4],
  1722. sDate = dayStart,
  1723. eDate = dayEnd
  1724. )
  1725. return retVal
  1726. pre_save.connect(makeSlug, Person)
  1727. class PersonDetail(models.Model):
  1728. """
  1729. Class PersonDetail(models.Model)
  1730. PersonDetail class holds data for MP and specific term.
  1731. These attributes are fetched from DB:
  1732. region - foreign key, which referes to division
  1733. person - foreign key, which referes to person
  1734. term - foreign key, which referes to term, the record is bound to
  1735. photo - of given MP
  1736. """
  1737. region = models.ForeignKey(Division, blank = True, null = True)
  1738. person = models.ForeignKey(Person)
  1739. term = models.ForeignKey(TermOfOffice)
  1740. photo = models.ImageField( _("Photo"),
  1741. upload_to = settings.MP_PHOTOS
  1742. )
  1743. class Meta:
  1744. """
  1745. See http://docs.djangoproject.com/en/dev/ref/models/options/
  1746. for details
  1747. """
  1748. #suppress class has no __init__ method
  1749. #pylint: disable-msg=W0232
  1750. #suppress too few public methods warning
  1751. #pylint: disable-msg=R0903
  1752. verbose_name = _("Person's Additional Info")
  1753. verbose_name_plural = _("Person's Additional Info")
  1754. class GroupManager(models.Manager):
  1755. """
  1756. Class GroupManager(models.Manager)
  1757. The GroupManager is a default custom manager for class Group.
  1758. """
  1759. #suppress 'method could be a function' refactor warning
  1760. #pylint: disable-msg=R0201
  1761. def getGroupWithStats(self, term, qType, poll):
  1762. """
  1763. Method getGroupWithStats(term, qType, poll)
  1764. Method retrives a list of groups extended by stat attribute,
  1765. which is an instance of GroupStats class. The arguments are:
  1766. term - TermOfOffice instance
  1767. qType - django.db.models.Q object - it is used to perform
  1768. custom filtering by group type
  1769. poll - instance of Poll class, it presents the particular
  1770. poll we want to gather data for
  1771. Returns the list of groups extended by stat attribute (instance of
  1772. GroupStats). The groups are ordered by name.
  1773. """
  1774. retVal = []
  1775. #suppress 'no objects member' warning
  1776. #pylint: disable-msg=E1101
  1777. for g in Group.objects.filter(
  1778. Q(term = term),
  1779. qType).order_by('name'):
  1780. try:
  1781. #suppress 'no objects member' warning
  1782. #pylint: disable-msg=E1101
  1783. g.stat = GroupStats.objects.get(group = g, poll = poll)
  1784. except ObjectDoesNotExist:
  1785. g.stat = None
  1786. subGroups = []
  1787. for sg in g.children.all():
  1788. try:
  1789. #suppress 'no objects member' warning
  1790. #pylint: disable-msg=E1101
  1791. sg.stat = GroupStats.objects.get(
  1792. group = sg,
  1793. poll = poll
  1794. )
  1795. except ObjectDoesNotExist:
  1796. g.stat = None
  1797. subGroups.append(sg)
  1798. g.subGroups = subGroups
  1799. retVal.append(g)
  1800. return retVal
  1801. def getGroupWithStatsArg(self, poll, qObjs):
  1802. """
  1803. Method getGroupWithStatsArg(poll, qObjs)
  1804. Method retrieves the list of groups. Each Group instance in the list
  1805. will be extended by attribute stat (instance of GroupStats class). The
  1806. method allows caller to finely define the desired querset parameter by
  1807. using qObjs argument. The arguments are as follows:
  1808. poll - the poll we want to gather stats for
  1809. qObjs - the args for filter() method from queryset,
  1810. it's basically tupple of django.db.models.Q instances
  1811. Returns list of groups extended by stat attribute, which is an instance
  1812. of GroupStats class.
  1813. """
  1814. retVal = []
  1815. #suppress 'no objects member' warning
  1816. #pylint: disable-msg=E1101
  1817. #suppress used * or ** magic
  1818. #pylint: disable-msg=W0142
  1819. for g in Group.objects.filter(*qObjs).order_by('name'):
  1820. try:
  1821. #suppress 'no objects member' warning
  1822. #pylint: disable-msg=E1101
  1823. g.stat = GroupStats.objects.get(group = g, poll = poll)
  1824. except ObjectDoesNotExist:
  1825. g.stat = None
  1826. subGroups = []
  1827. for sg in g.children.all():
  1828. try:
  1829. #suppress 'no objects member' warning
  1830. #pylint: disable-msg=E1101
  1831. sg.stat = GroupStats.objects.get(
  1832. group = sg,
  1833. poll = poll
  1834. )
  1835. except ObjectDoesNotExist:
  1836. sg.stat = None
  1837. subGroups.append(sg)
  1838. g.subGroups = subGroups
  1839. retVal.append(g)
  1840. return retVal
  1841. class Group(AutoSlug):
  1842. """
  1843. Class Group(AutoSlug)
  1844. Group represents any group of MP's, which participates in parliament
  1845. in given term. These attributes are stored into DB:
  1846. term - ForeignKey to term
  1847. members - ManyToManyField links person through Membership
  1848. table
  1849. groupId - group ID (key) from psp.cz
  1850. name - group name used in jakhlasovali.cz site
  1851. origName - group name used in psp.cz site.
  1852. type - type of group can be: party, board, subboard, ...
  1853. coulor - colour assigned to group when generating charts,
  1854. currently is used just for party clubs
  1855. icon - icon assigned to group (currently unused)
  1856. slug - slug field to generate human friendly urls
  1857. Certain queries will compute extra attributes:
  1858. group consistency, votes, ...
  1859. The Group class is using GroupManager as a default manager.
  1860. """
  1861. term = models.ForeignKey(TermOfOffice)
  1862. members = models.ManyToManyField( Person,
  1863. through='Membership',
  1864. blank = True,
  1865. null = True
  1866. )
  1867. parentGroup = models.ForeignKey( 'self',
  1868. related_name = 'children',
  1869. blank = True,
  1870. null = True
  1871. )
  1872. groupId = models.CharField(_("Group Id"), max_length = 20)
  1873. name = models.CharField(_("Name"), max_length = 512)
  1874. origName = models.CharField(_("Name"), max_length = 1024)
  1875. type = models.CharField(_("Club Type"), max_length = 50, blank = True)
  1876. color = models.CharField(_("Colour"), max_length = 6, blank = True)
  1877. icon = models.ImageField(_("Club Icon"),
  1878. upload_to = settings.GROUP_ICONS,
  1879. blank = True
  1880. )
  1881. slug = models.SlugField(db_index = True)
  1882. slugs = {
  1883. 'slug' : ('name', )
  1884. }
  1885. consistency = None
  1886. activeConsistency = None
  1887. votes = None
  1888. objects = GroupManager()
  1889. class Meta:
  1890. """
  1891. See http://docs.djangoproject.com/en/dev/ref/models/options/
  1892. for details
  1893. """
  1894. #suppress class has no __init__ method
  1895. #pylint: disable-msg=W0232
  1896. #suppress too few public methods warning
  1897. #pylint: disable-msg=R0903
  1898. verbose_name = _("MP Organ (party/board/...)")
  1899. verbose_name_plural = _("MP Organs (parties/boards/...)")
  1900. unique_together = (('term', 'groupId',),)
  1901. class Admin:
  1902. """
  1903. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  1904. for details
  1905. """
  1906. #suppress class has no __init__ method
  1907. #pylint: disable-msg=W0232
  1908. #suppress too few public methods warning
  1909. #pylint: disable-msg=R0903
  1910. pass
  1911. def getMembersCommon(self,
  1912. dateStart = None,
  1913. dateEnd = None,
  1914. orderArg = None,
  1915. distinct = True):
  1916. """
  1917. Method getMembersCommon(dateStart = None, dateEnd = None,
  1918. orderArg = None, distinct = True)
  1919. Returns list of person objects - members of group. If no date is
  1920. specified returns summary for complete term, otherwise returns member
  1921. list valid for particular date range. If dateStart only is specified,
  1922. then actual list for given date is returned.
  1923. Each person object in list returned is extended by these attributes:
  1924. mship,
  1925. days,
  1926. and:
  1927. age,
  1928. partyClub,
  1929. division,
  1930. ...
  1931. [ all attributes inserted by PersonManager.commonSelect ]
  1932. """
  1933. mships = None
  1934. if dateStart == None and dateEnd == None:
  1935. #suppress 'no membership_set member' warning
  1936. #pylint: disable-msg=E1101
  1937. mships = self.membership_set.all()
  1938. elif dateStart != None and dateEnd == None:
  1939. #suppress 'no membership_set member' warning
  1940. #pylint: disable-msg=E1101
  1941. mships = self.membership_set.filter(
  1942. Q(end = None) | Q(end__gte = dateStart),
  1943. start__lte = dateStart
  1944. )
  1945. elif dateStart != None and dateEnd != None:
  1946. #suppress 'no membership_set member' warning
  1947. #pylint: disable-msg=E1101
  1948. mships = self.membership_set.filter(
  1949. ~Q(end = None),
  1950. Q(end__gte = dateEnd),
  1951. start__lte = dateStart
  1952. )
  1953. else:
  1954. return []
  1955. if orderArg:
  1956. mships = mships.order_by(orderArg)
  1957. #
  1958. # we have to fake distinct by person, if we are asked to do so
  1959. #
  1960. if distinct:
  1961. tmp = {}
  1962. for m in mships:
  1963. tmp[m.person.id] = m
  1964. mships = []
  1965. for k in tmp.keys():
  1966. mships.append(tmp[k])
  1967. retVal = []
  1968. for m in mships:
  1969. #suppress 'no objects member' warning
  1970. #pylint: disable-msg=E1101
  1971. person = Person.objects.getPerson(m.person, self.term)
  1972. person.mship = m
  1973. person.start = m.start
  1974. person.end = m.end
  1975. person.post = m.post
  1976. if m.end == None:
  1977. person.days = (datetime.date.today() - m.start).days
  1978. else:
  1979. person.days = (m.end - m.start).days
  1980. retVal.append(person)
  1981. return retVal
  1982. def getMembersByAge(self, desc = True, dateStart = None,
  1983. dateEnd = None, distinct = True):
  1984. """
  1985. Method getMembersByAge(desc = True, dateStart = None
  1986. dateEnd = None, distinct = True)
  1987. Uses getMembersByAge() to retrieve list of persons (members of group)
  1988. ordered by age. The arguments are as follows:
  1989. desc - if true the members will be ordered from the oldest
  1990. to youngest
  1991. dateStart - date we want to retrieve members for
  1992. dateEnd - if date end is specified then method wil retrieve
  1993. persons for <dateStart, dateEnd> range.
  1994. if no date is specified members for complete term
  1995. are returned
  1996. distinct - only one membership will be retreived for person,
  1997. person might have several memberships, which can
  1998. overlap (member, prime member, ...)
  1999. Returns list of persons, it uses commonSelect to construct list.
  2000. """
  2001. retVal = self.getMembersCommon(dateStart, dateEnd,
  2002. distinct = distinct
  2003. )
  2004. if desc:
  2005. retVal.sort(lambda a, b: a.age - b.age)
  2006. else:
  2007. retVal.sort(lambda a, b: b.age - a.age)
  2008. return retVal
  2009. #suppress warning 'Unused argument distinct'
  2010. #pylint: disable-msg=W0613
  2011. def getMembersByParty(self, desc = False, dateStart = None,
  2012. dateEnd = None, distinct = True):
  2013. """
  2014. Method getMembersByParty(desc = True, dateStart = None
  2015. dateEnd = None, distinct = True)
  2016. Uses getMembersByParty() to retrieve list of persons (members of group)
  2017. ordered by party they member of. The arguments are as follows:
  2018. desc - if false the members will be ordered by party name in
  2019. alphabetical order
  2020. dateStart - date we want to retrieve members for
  2021. dateEnd - if date end is specified then method wil retrieve
  2022. persons for <dateStart, dateEnd> range.
  2023. if no date is specified members for complete term
  2024. are returned
  2025. distinct - only one membership will be retreived for person,
  2026. person might have several memberships, which can
  2027. overlap (member, prime member, ...)
  2028. Returns list of persons, it uses commonSelect to construct list.
  2029. """
  2030. retVal = self.getMembersCommon(dateStart, dateEnd,
  2031. distinct = True
  2032. )
  2033. if desc:
  2034. retVal.sort(lambda a, b: a.partyName > b.partyName)
  2035. else:
  2036. retVal.sort(lambda a, b: a.partyName < b.partyName)
  2037. return retVal
  2038. def getMembersByName(self, desc = False, dateStart = None,
  2039. dateEnd = None, distinct = True):
  2040. """
  2041. Method getMembersByName(desc = True, dateStart = None
  2042. dateEnd = None, distinct = True)
  2043. Uses getMembersByName() to retrieve list of persons (members of group)
  2044. ordered by their names. The arguments are as follows:
  2045. desc - if false the members will be ordered by name in
  2046. alphabetical order.
  2047. dateStart - date we want to retrieve members for
  2048. dateEnd - if date end is specified then method wil retrieve
  2049. persons for <dateStart, dateEnd> range.
  2050. if no date is specified members for complete term
  2051. are returned
  2052. distinct - only one membership will be retreived for person,
  2053. person might have several memberships, which can
  2054. overlap (member, prime member, ...)
  2055. Returns list of persons, it uses commonSelect to construct list.
  2056. """
  2057. retVal = None
  2058. if desc:
  2059. retVal = self.getMembersCommon(
  2060. dateStart,
  2061. dateEnd,
  2062. '-person__name',
  2063. distinct = distinct
  2064. )
  2065. else:
  2066. retVal = self.getMembersCommon(
  2067. dateStart,
  2068. dateEnd,
  2069. 'person__name',
  2070. distinct = distinct
  2071. )
  2072. return retVal
  2073. def getMembersBySurname(self, desc = False, dateStart = None,
  2074. dateEnd = None, distinct = True):
  2075. """
  2076. Method getMembersBySurname(desc = True, dateStart = None
  2077. dateEnd = None, distinct = True)
  2078. Uses getMembersBySurname() to retrieve list of persons (members of
  2079. group) ordered by their surname. The arguments are as follows:
  2080. desc - if false the members will be ordered by surname in
  2081. alphabetical order.
  2082. dateStart - date we want to retrieve members for
  2083. dateEnd - if date end is specified then method wil retrieve
  2084. persons for <dateStart, dateEnd> range.
  2085. if no date is specified members for complete term
  2086. are returned
  2087. distinct - only one membership will be retreived for person,
  2088. person might have several memberships, which can
  2089. overlap (member, prime member, ...)
  2090. Returns list of persons, it uses commonSelect to construct list.
  2091. """
  2092. retVal = None
  2093. if desc:
  2094. retVal = self.getMembersCommon(
  2095. dateStart,
  2096. dateEnd,
  2097. '-person__surname',
  2098. distinct = distinct
  2099. )
  2100. else:
  2101. retVal = self.getMembersCommon(
  2102. dateStart,
  2103. dateEnd,
  2104. 'person__surname',
  2105. distinct = distinct
  2106. )
  2107. return retVal
  2108. def getMembersByDivision(self, desc = False, dateStart = None,
  2109. dateEnd = None, distinct = True):
  2110. """
  2111. Method getMembersByDivision(desc = True, dateStart = None
  2112. dateEnd = None, distinct = True)
  2113. Uses getMembersByDivision() to retrieve list of persons (members of
  2114. group) ordered by the electoral division. The arguments are as follows:
  2115. desc - if false the members will be ordered by division name
  2116. in alphabetical order.
  2117. dateStart - date we want to retrieve members for
  2118. dateEnd - if date end is specified then method wil retrieve
  2119. persons for <dateStart, dateEnd> range.
  2120. if no date is specified members for complete term
  2121. are returned
  2122. distinct - only one membership will be retreived for person,
  2123. person might have several memberships, which can
  2124. overlap (member, prime member, ...)
  2125. Returns list of persons, it uses commonSelect to construct list.
  2126. """
  2127. retVal = self.getMembersCommon(dateStart, dateEnd, distinct = distinct)
  2128. if desc:
  2129. retVal.sort(lambda a, b: a.divisionName > b.divisionName)
  2130. else:
  2131. retVal.sort(lambda a, b: a.divisionName < b.divisionName)
  2132. return retVal
  2133. def getMembersByDays(self, desc = False, dateStart = None,
  2134. dateEnd = None, distinct = True):
  2135. """
  2136. Method getMembersByDays(desc = True, dateStart = None
  2137. dateEnd = None, distinct = True)
  2138. Uses getMembersByDays() to retrieve list of persons (members of group)
  2139. ordered by the number of days their membership lasts. The arguments
  2140. are as follows:
  2141. desc - if true the members will be ordered from the longest
  2142. membership to the shortest one.
  2143. dateStart - date we want to retrieve members for
  2144. dateEnd - if date end is specified then method wil retrieve
  2145. persons for <dateStart, dateEnd> range.
  2146. if no date is specified members for complete term
  2147. are returned
  2148. distinct - only one membership will be retreived for person,
  2149. person might have several memberships, which can
  2150. overlap (member, prime member, ...)
  2151. Returns list of persons, it uses commonSelect to construct list.
  2152. """
  2153. retVal = self.getMembersCommon(dateStart, dateEnd, distinct = distinct)
  2154. if desc:
  2155. retVal.sort(lambda a, b: a.days > b.days)
  2156. else:
  2157. retVal.sort(lambda a, b: a.days < b.days)
  2158. return retVal
  2159. def getMembersByStart(self, desc = False, dateStart = None,
  2160. dateEnd = None, distinct = True):
  2161. """
  2162. Method getMembersByStart(desc = True, dateStart = None
  2163. dateEnd = None, distinct = True)
  2164. Uses getMembersByStart() to retrieve list of persons (members of group)
  2165. ordered by the start of their membership. The arguments are as
  2166. follows:
  2167. desc - if false the members will be ordered from the oldest date
  2168. to recent ones
  2169. dateStart - date we want to retrieve members for
  2170. dateEnd - if date end is specified then method wil retrieve
  2171. persons for <dateStart, dateEnd> range.
  2172. if no date is specified members for complete term
  2173. are returned
  2174. distinct - only one membership will be retreived for person,
  2175. person might have several memberships, which can
  2176. overlap (member, prime member, ...)
  2177. Returns list of persons, it uses commonSelect to construct list.
  2178. """
  2179. retVal = None
  2180. if desc:
  2181. retVal = self.getMembersCommon(
  2182. dateStart,
  2183. dateEnd,
  2184. '-start',
  2185. distinct = distinct
  2186. )
  2187. else:
  2188. retVal = self.getMembersCommon(
  2189. dateStart,
  2190. dateEnd,
  2191. 'start',
  2192. distinct = distinct
  2193. )
  2194. return retVal
  2195. def getMembersByEnd(self, desc = False, dateStart = None,
  2196. dateEnd = None, distinct = True):
  2197. """
  2198. Method getMembersByEnd(desc = True, dateStart = None
  2199. dateEnd = None, distinct = True)
  2200. Uses getMembersByEnd() to retrieve list of persons (members of group)
  2201. ordered by the end date of their membership. The arguments are as
  2202. follows:
  2203. desc - if false the members will be ordered from the oldest
  2204. end dates to recent ones
  2205. dateStart - date we want to retrieve members for
  2206. dateEnd - if date end is specified then method wil retrieve
  2207. persons for <dateStart, dateEnd> range.
  2208. if no date is specified members for complete term
  2209. are returned
  2210. distinct - only one membership will be retreived for person,
  2211. person might have several memberships, which can
  2212. overlap (member, prime member, ...)
  2213. Returns list of persons, it uses commonSelect to construct list.
  2214. """
  2215. retVal = None
  2216. if desc:
  2217. retVal = self.getMembersCommon(
  2218. dateStart,
  2219. dateEnd,
  2220. '-end',
  2221. distinct = distinct
  2222. )
  2223. else:
  2224. retVal = self.getMembersCommon(
  2225. dateStart,
  2226. dateEnd,
  2227. 'end',
  2228. distinct = distinct
  2229. )
  2230. return retVal
  2231. def getMembersByPost(self, desc = False, dateStart = None,
  2232. dateEnd = None, distinct = True):
  2233. """
  2234. Method getMembersByPost(desc = True, dateStart = None
  2235. dateEnd = None, distinct = True)
  2236. Uses getMembersByPost() to retrieve list of persons (members of group)
  2237. ordered by their membership post (member (člen), head (předseda)). The
  2238. arguments are as follows:
  2239. desc - if false the members will be ordered from the oldest
  2240. end dates to recent ones
  2241. dateStart - date we want to retrieve members for
  2242. dateEnd - if date end is specified then method wil retrieve
  2243. persons for <dateStart, dateEnd> range.
  2244. if no date is specified members for complete term
  2245. are returned
  2246. distinct - only one membership will be retreived for person,
  2247. person might have several memberships, which can
  2248. overlap (member, prime member, ...)
  2249. Returns list of persons, it uses commonSelect to construct list.
  2250. """
  2251. retVal = None
  2252. if desc:
  2253. retVal = self.getMembersCommon(
  2254. dateStart,
  2255. dateEnd,
  2256. '-post',
  2257. distinct = distinct
  2258. )
  2259. else:
  2260. retVal = self.getMembersCommon(
  2261. dateStart,
  2262. dateEnd,
  2263. 'post',
  2264. distinct = distinct
  2265. )
  2266. return retVal
  2267. def getMembersByAbsences(self, desc = False, dateStart = None,
  2268. dateEnd = None, distinct = True):
  2269. """
  2270. Method getMembersByAbsences(desc = True, dateStart = None
  2271. dateEnd = None, distinct = True)
  2272. Uses getMembersByAbsences() to retrieve list of persons (members of group)
  2273. ordered by their absences. The arguments are as follows:
  2274. desc - if false the members will be ordered from the highest
  2275. absences to the lowest absences number.
  2276. dateStart - date we want to retrieve members for
  2277. dateEnd - if date end is specified then method wil retrieve
  2278. persons for <dateStart, dateEnd> range.
  2279. if no date is specified members for complete term
  2280. are returned
  2281. distinct - only one membership will be retreived for person,
  2282. person might have several memberships, which can
  2283. overlap (member, prime member, ...)
  2284. Returns list of persons, it uses commonSelect to construct list.
  2285. """
  2286. retVal = self.getMembersCommon(dateStart, dateEnd, distinct = distinct)
  2287. if desc:
  2288. retVal.sort(
  2289. lambda a, b: int(a.votes.totalAbsences - b.votes.totalAbsences)
  2290. )
  2291. else:
  2292. retVal.sort(
  2293. lambda a, b: int(b.votes.totalAbsences - a.votes.totalAbsences)
  2294. )
  2295. return retVal
  2296. def getGroupPartyProfile( self,
  2297. dateStart = None,
  2298. dateEnd = None):
  2299. """
  2300. Method getGroupPartyProfile(dateStart = None, dateEnd = None)
  2301. Returns list of parties the members of group are coming from. Each party
  2302. group in list is extended by these parameters
  2303. mpCount
  2304. group - which is the self (the group we are braking down to parties rep)
  2305. totalCount - number of members in self
  2306. percent - relative percent of party members in self
  2307. The arguments are:
  2308. dateStart - retrieves data for particular date
  2309. dateEnd - if specified retrieves data for range
  2310. <dateStart, dateEnd>
  2311. Returns list of party groups extended by mpCount, group, totalCount, percent
  2312. """
  2313. #suppress warning too many local variables
  2314. #pylint: disable-msg=R0914
  2315. if self.type == 'KLUB':
  2316. return None
  2317. select = None
  2318. if dateStart == None and dateEnd == None:
  2319. select = """
  2320. SELECT groupId, SUM(num) FROM (
  2321. SELECT partyMship.group_id AS groupId,
  2322. COUNT(DISTINCT mp.id) AS num
  2323. FROM psp_person AS mp,
  2324. psp_membership AS mship
  2325. JOIN psp_group AS party ON
  2326. party.type LIKE 'KLUB' AND
  2327. party.term_id = %d
  2328. JOIN psp_membership AS partyMship ON
  2329. mp.id = partyMship.person_id AND
  2330. partyMship.group_id = party.id
  2331. WHERE mship.group_id = %d AND mship.person_id = mp.id
  2332. GROUP BY mp.id ORDER BY partyMship.group_id
  2333. )
  2334. GROUP BY groupId;
  2335. """
  2336. #suppress warning ForeignKey has no attribute id
  2337. #pylint: disable-msg=E1101
  2338. #suppress warning Group has no attribute id
  2339. #pylint: disable-msg=E1101
  2340. select = select % (self.term.id, self.id)
  2341. elif dateStart != None and dateEnd == None:
  2342. date = '%d-%02d-%02d' % ( dateStart.year,
  2343. dateStart.month,
  2344. dateStart.day
  2345. )
  2346. select = """
  2347. SELECT groupId, SUM(num) FROM (
  2348. SELECT partyMship.group_id AS groupId,
  2349. COUNT(DISTINCT mp.id) AS num
  2350. FROM psp_person AS mp,
  2351. psp_membership AS mship
  2352. JOIN psp_group AS party ON
  2353. party.type LIKE 'KLUB' AND party.term_id = %d
  2354. JOIN psp_membership AS partyMship ON
  2355. mp.id = partyMship.person_id AND
  2356. partyMship.group_id = party.id AND
  2357. ( (partyMship.end IS NULL AND
  2358. partyMship.start <= '%s') OR
  2359. ('%s' BETWEEN partyMship.start AND
  2360. partyMship.end)
  2361. )
  2362. WHERE mship.group_id = %d AND
  2363. mship.person_id = mp.id AND
  2364. ( (mship.end IS NULL AND
  2365. mship.start <= '%s') OR
  2366. ('%s' BETWEEN mship.start AND mship.end)
  2367. )
  2368. GROUP BY mp.id ORDER BY partyMship.group_id
  2369. )
  2370. GROUP BY groupId;
  2371. """
  2372. #suppress warning ForeignKey has no attribute id
  2373. #pylint: disable-msg=E1101
  2374. #suppress warning Group has no attribute id
  2375. #pylint: disable-msg=E1101
  2376. select = select % ( self.term.id,
  2377. date, date,
  2378. self.id,
  2379. date, date
  2380. )
  2381. elif dateStart != None and dateEnd != None:
  2382. start = '%d-%02d-%02d' % ( dateStart.year,
  2383. dateStart.month,
  2384. dateStart.day
  2385. )
  2386. end = '%d-%02d-%02d' % ( dateEnd.year,
  2387. dateEnd.month,
  2388. dateEnd.day
  2389. )
  2390. select = """
  2391. SELECT groupId, SUM(num) FROM (
  2392. SELECT partyMship.group_id AS groupId,
  2393. COUNT(DISTINCT mp.id) AS num
  2394. FROM psp_person AS mp, psp_membership AS mship
  2395. JOIN psp_group AS party ON
  2396. party.type LIKE 'KLUB' AND
  2397. party.term_id = %d
  2398. JOIN psp_membership AS partyMship ON
  2399. mp.id = partyMship.person_id AND
  2400. partyMship.group_id = party.id AND
  2401. ((partyMship.end IS NULL AND
  2402. (partyMship.start BETWEEN '%s' AND '%s' OR
  2403. partyMship.start <= '%s'))
  2404. OR
  2405. (partyMship.start BETWEEN '%s' AND '%s' OR
  2406. partyMship.end BETWEEN '%s' AND '%s') OR
  2407. (partyMship.start <= '%s'))
  2408. WHERE mship.group_id = %d AND
  2409. mship.person_id = mp.id AND
  2410. ((mship.end IS NULL AND
  2411. (mship.start BETWEEN '%s' AND '%s' OR
  2412. mship.start <= '%s'))
  2413. OR
  2414. (mship.start BETWEEN '%s' AND '%s' OR
  2415. mship.end BETWEEN '%s' AND '%s') OR
  2416. (mship.start <= '%s')
  2417. )
  2418. GROUP BY mp.id ORDER BY partyMship.group_id
  2419. )
  2420. GROUP BY groupId;
  2421. """
  2422. #suppress warning ForeignKey has no attribute id
  2423. #pylint: disable-msg=E1101
  2424. #suppress warning Group has no attribute id
  2425. #pylint: disable-msg=E1101
  2426. select = select % ( self.term.id,
  2427. start, end, start,
  2428. start, end, start, end,
  2429. start,
  2430. self.id,
  2431. start, end, start,
  2432. start, end, start, end,
  2433. start,
  2434. )
  2435. else:
  2436. raise ObjectDoesNotExist
  2437. cursor = connection.cursor()
  2438. cursor.execute(select)
  2439. rows = cursor.fetchall()
  2440. retVal = []
  2441. total = 0
  2442. for row in rows:
  2443. groupId = row[0]
  2444. mpCount = row[1]
  2445. total += mpCount
  2446. #suppress 'no objects member' warning
  2447. #pylint: disable-msg=E1101
  2448. group = Group.objects.get(id = groupId)
  2449. group.mpCount = mpCount
  2450. group.group = self
  2451. retVal.append(group)
  2452. for g in retVal:
  2453. g.totalCount = total
  2454. g.percent = get_percents(g.mpCount, total)
  2455. return retVal
  2456. def getMfRatio(self):
  2457. """
  2458. Method getMfRatio()
  2459. Returns the ratio between men and women in the group.
  2460. """
  2461. males = 0
  2462. females = 0
  2463. members = None
  2464. #suppress 'no membership_set member' warning
  2465. #pylint: disable-msg=E1101
  2466. member_ids = self.membership_set.all().values('person_id').distinct()
  2467. for m_id in member_ids:
  2468. member = Person.objects.get(id = m_id['person_id'])
  2469. if member.sex == 'F':
  2470. females += 1
  2471. else:
  2472. males += 1
  2473. return males, females
  2474. def getMfPie(self):
  2475. """
  2476. Method getMfPie()
  2477. Returns the GChart.Pie3D instance - chart representing ratio between
  2478. men and women in the group.
  2479. """
  2480. male, female = self.getMfRatio()
  2481. pm = get_percents(male, male + female)
  2482. pf = get_percents(female, male + female)
  2483. retVal = Pie3D([pm, pf])
  2484. retVal.label(
  2485. _(u'Muži') + ' %d' % male,
  2486. _(u'Ženy') + ' %d' % female
  2487. )
  2488. retVal.color('000077', '770000')
  2489. #suppress warning foreign key has no member getStart()
  2490. #pylint: disable-msg=E1101
  2491. #suppress warning foreign key has no meber getEnd()
  2492. #pylint: disable-msg=E1101
  2493. retVal.title(_(u'Poměr mužů a žen') + '(%d - %d)' % (
  2494. self.term.getStart(),
  2495. self.term.getEnd())
  2496. )
  2497. retVal.size(330, 130)
  2498. return retVal
  2499. def getPartyName(self):
  2500. """
  2501. Method getPartyName()
  2502. Method converts long name format to acronum. The mpClub.name usually
  2503. looks like
  2504. Poslanecký klub Občasnské demokratické strany
  2505. the method will turn it into abbreviation:
  2506. ODS
  2507. Returns string with party name acronum if group is party.
  2508. """
  2509. #work around Křesťansko demokratická strana-českosloven...
  2510. #suppress warning foreign key has no meber replace
  2511. #pylint: disable-msg=E1101
  2512. name = self.origName.replace('-', ' - ')
  2513. pattern = re.compile(u'.*Poslanecký klub(?P<name>.*)', re.U)
  2514. match = pattern.match(name)
  2515. retVal = u''
  2516. if match:
  2517. name = match.group('name')
  2518. for i in name.split():
  2519. if i == 'a' or i == 'A':
  2520. continue
  2521. retVal += i[0].upper()
  2522. if retVal == 'N':
  2523. retVal = _(u'Nezávislí')
  2524. return retVal.strip()
  2525. def getAvgAge(self):
  2526. """
  2527. Method getAvgAge()
  2528. Returns the average age of group members.
  2529. """
  2530. retVal = 0
  2531. totalAge = 0
  2532. count = 0
  2533. #suppress 'no membership_set member' warning
  2534. #pylint: disable-msg=E1101
  2535. members = self.membership_set.all().distinct('person')
  2536. for member in members:
  2537. #suppress 'no objects member' warning
  2538. #pylint: disable-msg=E1101
  2539. mp = Person.objects.get(id = member.person.id)
  2540. age = self.term.getStart() - mp.birthDate.year
  2541. totalAge += age
  2542. count += 1
  2543. if count != 0:
  2544. retVal = totalAge / count
  2545. return retVal
  2546. def getPartyPie(self, dateStart = None, dateEnd = None):
  2547. """
  2548. Method getPartyPie(dateStart = None, dateEnd = None)
  2549. Method returns party profile for group. It generates party profile
  2550. either for single date, if dateStart only is specified or for
  2551. <dateStart, dateEnd> range if both arguments are specified.
  2552. If no argument is passed, then chart for complete term is
  2553. returned.
  2554. """
  2555. partyProfile = self.getGroupPartyProfile(dateStart, dateEnd)
  2556. if partyProfile == None:
  2557. return None
  2558. pieData = [ party.mpCount for party in partyProfile ]
  2559. retVal = Pie3D(pieData)
  2560. label = [ '%s (%.2f%%)' % ( party.getPartyName(),
  2561. party.percent
  2562. ) for party in partyProfile
  2563. ]
  2564. #suppress used * or ** magic
  2565. #pylint: disable-msg=W0142
  2566. retVal.label(*label)
  2567. colors = ''
  2568. for party in partyProfile:
  2569. colors += party.color + '|'
  2570. retVal.color(colors.strip('|'))
  2571. if dateStart == None and dateEnd == None:
  2572. #suppress warning foreign key has no member getStart()
  2573. #pylint: disable-msg=E1101
  2574. #suppress warning foreign key has no meber getEnd()
  2575. #pylint: disable-msg=E1101
  2576. title = u'%s (%d - %d)' % (
  2577. self.name,
  2578. self.term.getStart(),
  2579. self.term.getEnd()
  2580. )
  2581. elif dateStart != None and dateEnd == None:
  2582. title = u'%s (%s)' % (
  2583. self.name,
  2584. dateStart.strftime("%d. %m. %Y")
  2585. )
  2586. else:
  2587. title = u'%s (%s - %s)' % (
  2588. self.name,
  2589. dateStart.strftime("%d. %m. %Y"),
  2590. dateEnd.strftime("%d. %m. %Y")
  2591. )
  2592. retVal.title(title)
  2593. retVal.size(400, 130)
  2594. return retVal
  2595. def getAccordanceWithGroup( self,
  2596. otherGroup,
  2597. dateStart = None,
  2598. dateEnd = None
  2599. ):
  2600. """
  2601. Method getAccordanceWithGroup(otherGroup,
  2602. dateStart = None, dateEnd = None)
  2603. Method generates accordance chart with 'otherGroup'.
  2604. The arguments are:
  2605. otherGroup - the group to be compared for accordance
  2606. dateStart - dateStart if not specified the accordance for
  2607. complete term is computed, otherwise for signle
  2608. dateStart day only is computed.
  2609. dateEnd - if specified then accordance for <dateStart,
  2610. dateEnd> range is returned.
  2611. Returns accordance with group chart.
  2612. """
  2613. #suppress 'no objects member' warning
  2614. #pylint: disable-msg=E1101
  2615. groupAccord = GroupStats.objects.getAccordanceWithGroup(
  2616. self, otherGroup,
  2617. dateStart, dateEnd
  2618. )
  2619. retVal = groupAccord.accordance.getChart()
  2620. title = ''
  2621. if self.type == 'KLUB':
  2622. title = _(u'%s - shoda s ') % (self.getPartyName())
  2623. else:
  2624. title = _(u'%s - shoda s ') % (self.name)
  2625. if otherGroup.type == 'KLUB':
  2626. title += otherGroup.getPartyName()
  2627. else:
  2628. title += otherGroup.name
  2629. retVal.title(title)
  2630. retVal.size(330, 80)
  2631. #suppress warning 'Attribute accordance defined outside of init'
  2632. #pylint: disable-msg=W0201
  2633. self.accordance = retVal
  2634. return retVal
  2635. def getPresencePieChart(self, dateStart = None, dateEnd = None):
  2636. """
  2637. Method getPresencePieChart(dateStart = None, dateEnd = None)
  2638. Extends self by votes attribute, which is an instance of Votes class.
  2639. If dateStart, dateEnd are None then presence pie chart for complete
  2640. term is computed. If both arguments are specified then presence
  2641. pie for <dateStart, dateEnd> range is returned.
  2642. Returns Pie3D presence chart for given group.
  2643. """
  2644. #suppress 'no objects member' warning
  2645. #pylint: disable-msg=E1101
  2646. stats = GroupStats.objects.getStats(
  2647. group = self,
  2648. dateStart = dateStart,
  2649. dateEnd = dateEnd
  2650. )
  2651. #suppress warning 'Attribute stats defined outside of init'
  2652. #pylint: disable-msg=W0201
  2653. self.stats = stats
  2654. retVal = stats.votes.getPresenceChart()
  2655. if self.type == 'KLUB':
  2656. retVal.title(_(u'%s - absence %d - %d') % (
  2657. self.getPartyName(),
  2658. self.term.getStart(),
  2659. self.term.getEnd()
  2660. )
  2661. )
  2662. else:
  2663. retVal.title(_(u'%s Absence %d - %d') % (
  2664. self.name,
  2665. self.term.getStart(),
  2666. self.term.getEnd()
  2667. )
  2668. )
  2669. retVal.size(330, 80)
  2670. return retVal
  2671. def getGroupConsistency(self, dateStart, dateEnd = None):
  2672. """
  2673. Method getGroupConsistency(dateStart, dateEnd = None)
  2674. Returns self extended by two attributes:
  2675. consistency
  2676. activeConsistency
  2677. both attributes are instances of Accordance class. Both attributes
  2678. describe poll conistency inside the given group - group consistency.
  2679. Group consistency is reflects the ratio between MPs, who voted with
  2680. group majority vs. MPs who voted against the final group result.
  2681. The activeConsistency does not account MP's absences.
  2682. The date range we want to gather consistency for is defined as
  2683. <dayStart, dayEnd> if no upper bound (dayEnd == None) is defined,
  2684. then consistency data for single date (dayStart) are retreived.
  2685. Method returns self on success, None on error.
  2686. """
  2687. def none2zero(x):
  2688. """
  2689. Internal function none2zero()
  2690. Accepts argument 'x' if x is equal to None returns zero, otherwise
  2691. returns x.
  2692. """
  2693. if x == None:
  2694. return 0
  2695. else:
  2696. return x
  2697. select = ''
  2698. if dateEnd == None:
  2699. select = """
  2700. SELECT SUM (CASE
  2701. WHEN gstats.result = vote.result THEN 1
  2702. ELSE 0
  2703. END
  2704. ) AS same,
  2705. SUM (CASE
  2706. WHEN gstats.result != vote.result THEN 1
  2707. ELSE 0
  2708. END
  2709. ) AS diff,
  2710. SUM (CASE
  2711. WHEN vote.result IN ('A', 'N', 'Z') THEN
  2712. CASE
  2713. WHEN gstats.result = vote.result THEN 1
  2714. ELSE 0
  2715. END
  2716. ELSE 0
  2717. END
  2718. ) AS sameNoAbs,
  2719. SUM (CASE
  2720. WHEN vote.result IN ('A', 'N', 'Z') THEN
  2721. CASE
  2722. WHEN gstats.result != vote.result THEN 1
  2723. ELSE 0
  2724. END
  2725. ELSE 0
  2726. END
  2727. ) AS diffNoAbs,
  2728. SUM (1) AS total
  2729. FROM psp_poll AS poll,
  2730. psp_membership AS mship
  2731. JOIN psp_groupstats AS gstats ON gstats.poll_id = poll.id AND
  2732. gstats.group_id = %d
  2733. JOIN psp_mpvote AS vote ON vote.person_id = mship.person_id AND
  2734. mship.group_id = %d AND vote.poll_id = poll.id
  2735. WHERE poll.date = '%d-%02d-%02d' AND mship.group_id = %d AND
  2736. mship.post LIKE '_len' AND
  2737. ((poll.date BETWEEN mship.start AND mship.end) OR
  2738. (mship.start <= poll.date AND mship.end IS NULL))
  2739. """
  2740. #suppress 'Group has no id member' warning
  2741. #pylint: disable-msg=E1101
  2742. select = select % ( self.id,
  2743. self.id,
  2744. dateStart.year, dateStart.month, dateStart.day,
  2745. self.id,
  2746. )
  2747. else:
  2748. select = """
  2749. SELECT SUM (CASE
  2750. WHEN gstats.result = vote.result THEN 1
  2751. ELSE 0
  2752. END
  2753. ) AS same,
  2754. SUM (CASE
  2755. WHEN gstats.result != vote.result THEN 1
  2756. ELSE 0
  2757. END
  2758. ) AS diff,
  2759. SUM (CASE
  2760. WHEN vote.result IN ('A', 'N', 'Z') THEN
  2761. CASE
  2762. WHEN gstats.result = vote.result THEN 1
  2763. ELSE 0
  2764. END
  2765. ELSE 0
  2766. END
  2767. ) AS sameNoAbs,
  2768. SUM (CASE
  2769. WHEN vote.result IN ('A', 'N', 'Z') THEN
  2770. CASE
  2771. WHEN gstats.result != vote.result THEN 1
  2772. ELSE 0
  2773. END
  2774. ELSE 0
  2775. END
  2776. ) AS diffNoAbs,
  2777. SUM (1) AS total
  2778. FROM psp_poll AS poll,
  2779. psp_membership AS mship
  2780. JOIN psp_groupstats AS gstats ON gstats.poll_id = poll.id AND
  2781. gstats.group_id = %d
  2782. JOIN psp_mpvote AS vote ON vote.person_id = mship.person_id AND
  2783. mship.group_id = %d AND vote.poll_id = poll.id
  2784. WHERE poll.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d' AND
  2785. mship.group_id = %d AND mship.post LIKE '_len' AND
  2786. ((poll.date BETWEEN mship.start AND mship.end) OR
  2787. (mship.start <= poll.date AND mship.end IS NULL));
  2788. """
  2789. #suppress 'Group has no id member' warning
  2790. #pylint: disable-msg=E1101
  2791. select = select % ( self.id,
  2792. self.id,
  2793. dateStart.year, dateStart.month, dateStart.day,
  2794. dateEnd.year, dateEnd.month, dateEnd.day,
  2795. self.id
  2796. )
  2797. cursor = connection.cursor()
  2798. cursor.execute(select)
  2799. rows = cursor.fetchall()
  2800. if len(rows) != 1:
  2801. return None
  2802. row = rows[0]
  2803. #suppress used builtin function 'map' warning
  2804. #pylint: disable-msg=W0141
  2805. row = map(none2zero, row)
  2806. self.consistency = Accordance(
  2807. same = row[0],
  2808. diff = row[1],
  2809. sDate = dateStart,
  2810. eDate = dateEnd
  2811. )
  2812. self.activeConsistency = Accordance(
  2813. same = row[2],
  2814. diff = row[3],
  2815. sDate = dateStart,
  2816. eDate = dateEnd
  2817. )
  2818. return self
  2819. pre_save.connect(makeSlug, Group)
  2820. class Membership(models.Model):
  2821. """
  2822. Class Membership(models.Model)
  2823. Class represents a membership of person in group.
  2824. These attributes are fetched from DB:
  2825. group - foreign key refering to group
  2826. person - foreign key refering to person
  2827. post - post bound to membership (member, head, ...)
  2828. start - start date of membership
  2829. end - end date of membership. if None then membership
  2830. is still in progress.
  2831. """
  2832. group = models.ForeignKey(Group)
  2833. person = models.ForeignKey(Person)
  2834. post = models.CharField(_("Post"), max_length = 30)
  2835. start = models.DateField(_("Start Date"))
  2836. end = models.DateField(_("End Date"), blank = True, null = True)
  2837. class Meta:
  2838. """
  2839. See http://docs.djangoproject.com/en/dev/ref/models/options/
  2840. for details
  2841. """
  2842. #suppress class has no __init__ method
  2843. #pylint: disable-msg=W0232
  2844. #suppress too few public methods warning
  2845. #pylint: disable-msg=R0903
  2846. verbose_name = _("MP Organ Membership")
  2847. verbose_name_plural = _("MP Organs Membership")
  2848. class Admin:
  2849. """
  2850. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  2851. for details
  2852. """
  2853. #suppress class has no __init__ method
  2854. #pylint: disable-msg=W0232
  2855. #suppress too few public methods warning
  2856. #pylint: disable-msg=R0903
  2857. pass
  2858. def getStart(self):
  2859. """
  2860. Method getStart() returns self.start
  2861. """
  2862. return self.start
  2863. def getEnd(self):
  2864. """
  2865. Method getEnd() returns self.end.
  2866. If self.end is None then datetime.datetime.today()
  2867. is returned.
  2868. """
  2869. if self.end == None:
  2870. return datetime.datetime.today()
  2871. return self.end
  2872. def getDuration(self):
  2873. """
  2874. Method getDuration() returns time delta between start and date.
  2875. """
  2876. if self.end == None:
  2877. today = datetime.datetime.today()
  2878. return today - self.start
  2879. return self.end - self.start
  2880. class Meeting(models.Model):
  2881. """
  2882. Class Meeting(models.Model)
  2883. Class meeting represents single meeting. These fields are fetched from DB:
  2884. term - ForeignKey to TermOfOffice, when particular team happened
  2885. meetingNo - the kind of meeting name (string) (i.e. '50', '51'...)
  2886. start - datetime.date instance - start date of meeting
  2887. end - datetime.date instance - end date of meeting, some meetings
  2888. can last more than one day
  2889. name - meeting name (i.e. '51. schuze')
  2890. url - link to meeting data at psp.cz
  2891. """
  2892. term = models.ForeignKey(TermOfOffice)
  2893. meetingNo = models.CharField(_("Meeting No."), max_length = 8)
  2894. start = models.DateField(_("Start Date"))
  2895. end = models.DateField(_("End Date"))
  2896. name = models.CharField(_("Session Name"), max_length = 80)
  2897. url = models.URLField(_("Link To Meeting Agenda"), verify_exists = False)
  2898. class Meta:
  2899. """
  2900. See http://docs.djangoproject.com/en/dev/ref/models/options/
  2901. for details
  2902. """
  2903. #suppress class has no __init__ method
  2904. #pylint: disable-msg=W0232
  2905. #suppress too few public methods warning
  2906. #pylint: disable-msg=R0903
  2907. verbose_name = _("Meeting")
  2908. verbose_name_plural = _("Meetings")
  2909. class Admin:
  2910. """
  2911. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  2912. for details
  2913. """
  2914. #suppress class has no __init__ method
  2915. #pylint: disable-msg=W0232
  2916. #suppress too few public methods warning
  2917. #pylint: disable-msg=R0903
  2918. pass
  2919. class PollManager(models.Manager):
  2920. """
  2921. Class PollManager(models.Manager)
  2922. Class is default manager for Polls.
  2923. """
  2924. #suppress 'method could be a function' refactor warning
  2925. #pylint: disable-msg=R0201
  2926. def getMeetingDates(self):
  2927. """
  2928. Method getMeetingDates()
  2929. Returns list of Poll instances grouped by date. Method is used in
  2930. updateDB.py so far.
  2931. """
  2932. select = """
  2933. SELECT date FROM psp_poll GROUP BY date ORDER BY date ASC;
  2934. """
  2935. retVal = []
  2936. cursor = connection.cursor()
  2937. cursor.execute(select)
  2938. rows = cursor.fetchall()
  2939. for row in rows:
  2940. retVal.append(row[0])
  2941. return retVal
  2942. class Poll(AutoSlug):
  2943. """
  2944. Class Poll(AutoSlug)
  2945. Class represents single poll. These attributes are fetched from DB:
  2946. meeting - meeting is ForeignKey referring to particular Meeting
  2947. instance
  2948. name - poll title
  2949. pollId - an ID assigned to poll at psp.cz
  2950. verdict - can be one of follows:
  2951. 'přijato'
  2952. 'zamítnuto'
  2953. votesAye - number of votes Aye
  2954. votesNay - number of votes Nay
  2955. votesRefrain - number of refrains
  2956. absences - number of absences
  2957. excused - number of excused absences
  2958. slug - slug referring to poll
  2959. resultsLink - link to results at psp.cz
  2960. stenoLink - link to stenographic record
  2961. date - datetime.date instance with poll date
  2962. """
  2963. meeting = models.ForeignKey(Meeting)
  2964. name = models.CharField(_("Poll Title"), max_length = 80)
  2965. pollId = models.CharField(_("Poll Id"), max_length = 30, db_index = True)
  2966. verdict = models.CharField(_("Verdict"), max_length = 20)
  2967. votesAye = models.IntegerField( _("Votes For"),
  2968. db_column = 'vote_aye'
  2969. )
  2970. votesNay = models.IntegerField( _("Votes Against"),
  2971. db_column = 'vote_nay'
  2972. )
  2973. votesRefrain = models.IntegerField( _("Votes Refrained"),
  2974. db_column = 'refrain'
  2975. )
  2976. absences = models.IntegerField( _("Absentees"),
  2977. db_column = 'absentees'
  2978. )
  2979. excused = models.IntegerField(_("Excused"))
  2980. slug = models.SlugField(db_index = True)
  2981. resultsLink = models.URLField( _("Link to Poll Result"),
  2982. verify_exists = False,
  2983. blank = True
  2984. )
  2985. stenoLink = models.URLField( _("Link to steno record"),
  2986. verify_exists = False,
  2987. blank = True
  2988. )
  2989. date = models.DateField(_("Poll date"), blank = True)
  2990. slugs = {
  2991. 'slug' : ('name', )
  2992. }
  2993. objects = PollManager()
  2994. class Meta:
  2995. """
  2996. See http://docs.djangoproject.com/en/dev/ref/models/options/
  2997. for details
  2998. """
  2999. #suppress class has no __init__ method
  3000. #pylint: disable-msg=W0232
  3001. #suppress too few public methods warning
  3002. #pylint: disable-msg=R0903
  3003. verbose_name = _("Poll")
  3004. verbose_name_plural = _("Polls")
  3005. class Admin:
  3006. """
  3007. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  3008. for details
  3009. """
  3010. #suppress class has no __init__ method
  3011. #pylint: disable-msg=W0232
  3012. #suppress too few public methods warning
  3013. #pylint: disable-msg=R0903
  3014. pass
  3015. def resultPie(self):
  3016. """
  3017. Method resultPie()
  3018. Method generates a pie chart representing results.
  3019. Returns instance GChart.Pie3D instance.
  3020. """
  3021. #suppress 'no objects member' warning
  3022. #pylint: disable-msg=E1101
  3023. mpVotes = MpVote.objects.filter(poll = self)
  3024. votes = Votes(
  3025. vAye = mpVotes.filter(result__iexact = 'A').count(),
  3026. vNay = mpVotes.filter(result__iexact = 'N').count(),
  3027. vRefrains = mpVotes.filter(result__iexact = 'Z').count(),
  3028. absences = mpVotes.filter(result__iregex = '0|X|M').count(),
  3029. sDate = self.date
  3030. )
  3031. retVal = votes.getVotesChart()
  3032. retVal.size(370, 120)
  3033. return retVal
  3034. def bumpPoll(self, result):
  3035. """
  3036. Method bumpPoll(result)
  3037. Bumps poll - result is code for result for particular MP.
  3038. The update will bump votesAye, votesNay ... by result code:
  3039. 'A' - adds one vote to votesAye
  3040. 'N' - adds one vote to votesNay
  3041. 'Z' - adds one vote to votesRefrain
  3042. '0' - adds one vote to absences
  3043. 'X'
  3044. 'M' - adds one vote for excused
  3045. Returns void
  3046. """
  3047. if result == 'A':
  3048. self.votesAye += 1
  3049. elif result == 'N':
  3050. self.votesNay += 1
  3051. elif result == 'Z':
  3052. self.votesRefrain += 1
  3053. elif result == '0' or result == 'X':
  3054. self.absences += 1
  3055. elif result == 'M':
  3056. self.excused += 1
  3057. return
  3058. def getRebels(self, active = False):
  3059. """
  3060. Method getRebels(active = False)
  3061. Method returns list persons. The list is ordered by accordance with
  3062. MP's clubs for given poll. The person instances (MPs) are ordered from
  3063. the least accordance. The argument active determines whether
  3064. all results shoudl be counted or only those, which are result of
  3065. active presence (results code 'A', 'N', 'Z').
  3066. Returns the list of persons ordered by accordance with party club
  3067. for given poll.
  3068. """
  3069. extraArg = ''
  3070. if active:
  3071. extraArg = """
  3072. AND mpvote.result IN ('A', 'N', 'Z')
  3073. """
  3074. select = """
  3075. SELECT mship.person_id
  3076. FROM psp_mpvote AS mpvote,
  3077. psp_group AS party
  3078. JOIN psp_groupstats AS gs ON gs.group_id = party.id AND gs.poll_id = %d
  3079. JOIN psp_membership AS mship ON mship.person_id = mpvote.person_id AND
  3080. mship.group_id = party.id AND mship.post LIKE '_len' AND
  3081. gs.group_id = mship.group_id AND gs.result != mpvote.result %s
  3082. WHERE mpvote.poll_id = %d AND party.type = 'KLUB'
  3083. GROUP BY mship.person_id;
  3084. """
  3085. #suppress warning Poll has no attribute id
  3086. #pylint: disable-msg=E1101
  3087. select = select % ( self.id,
  3088. extraArg,
  3089. self.id
  3090. )
  3091. cursor = connection.cursor()
  3092. cursor.execute(select)
  3093. rows = cursor.fetchall()
  3094. retVal = []
  3095. for row in rows:
  3096. #suppress 'no objects member' warning
  3097. #pylint: disable-msg=E1101
  3098. mp = Person.objects.get(id = row[0])
  3099. retVal.append(mp)
  3100. return retVal
  3101. class MpVoteManager(models.Manager):
  3102. """
  3103. Class MpVoteManager(models.Manager)
  3104. MpVoteManager is default manager for MpVote class.
  3105. """
  3106. #suppress 'method could be a function' refactor warning
  3107. #pylint: disable-msg=R0201
  3108. def getDayStats(self, dateStart, dateEnd = None):
  3109. """
  3110. Method getDayStats(dateStart, dateEnd = None)
  3111. Retrieves stats for any arbitrary period. dateStart is mandatory
  3112. argument, dateEnd is optional. When both arguments are specified
  3113. function retrieves stats for range <dateStart, dateEnd>
  3114. Returns VoteStats object. It's kind of surprise (one would expect it
  3115. will be returned by by VoteStats manager). The think is the
  3116. VoteStats aggregate records for whole term. Therefore I've decided
  3117. to put this method here.
  3118. """
  3119. if dateEnd != None:
  3120. select = """
  3121. SELECT SUM( CASE
  3122. WHEN mpvote.result = 'A' THEN 1
  3123. ELSE 0
  3124. END
  3125. ) AS votes_aye,
  3126. SUM( CASE
  3127. WHEN mpvote.result = 'N' THEN 1
  3128. ELSE 0
  3129. END
  3130. ) AS votes_nay,
  3131. SUM( CASE
  3132. WHEN mpvote.result = 'Z' THEN 1
  3133. ELSE 0
  3134. END
  3135. ) AS votes_refrains,
  3136. SUM( CASE
  3137. WHEN mpvote.result = '0' OR
  3138. mpvote.result = 'X' THEN 1
  3139. ELSE 0
  3140. END
  3141. ) AS absences,
  3142. SUM( CASE
  3143. WHEN mpvote.result = 'M' THEN 1
  3144. ELSE 0
  3145. END
  3146. ) AS absences_excused
  3147. FROM psp_poll AS poll
  3148. INNER JOIN psp_mpvote AS mpvote ON mpvote.poll_id = poll.id
  3149. WHERE poll.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d'
  3150. """ % ( dateStart.year, dateStart.month, dateStart.day,
  3151. dateEnd.year, dateEnd.month, dateEnd.day
  3152. )
  3153. else:
  3154. select = """
  3155. SELECT SUM( CASE
  3156. WHEN mpvote.result = 'A' THEN 1
  3157. ELSE 0
  3158. END
  3159. ) AS votes_aye,
  3160. SUM( CASE
  3161. WHEN mpvote.result = 'N' THEN 1
  3162. ELSE 0
  3163. END
  3164. ) AS votes_nay,
  3165. SUM( CASE
  3166. WHEN mpvote.result = 'Z' THEN 1
  3167. ELSE 0
  3168. END
  3169. ) AS votes_refrains,
  3170. SUM( CASE
  3171. WHEN mpvote.result = '0' OR
  3172. mpvote.result = 'X' THEN 1
  3173. ELSE 0
  3174. END
  3175. ) AS absences,
  3176. SUM( CASE
  3177. WHEN mpvote.result = 'M' THEN 1
  3178. ELSE 0
  3179. END
  3180. ) AS absences_excused
  3181. FROM psp_poll AS poll
  3182. INNER JOIN psp_mpvote AS mpvote ON mpvote.poll_id = poll.id
  3183. WHERE poll.date = '%d-%02d-%02d';
  3184. """ % (dateStart.year, dateStart.month, dateStart.day)
  3185. cursor = connection.cursor()
  3186. cursor.execute(select)
  3187. rows = cursor.fetchall()
  3188. if len(rows) != 1:
  3189. return None
  3190. row = rows[0]
  3191. retVal = VoteStats()
  3192. retVal.votesAye = row[0]
  3193. retVal.votesNay = row[1]
  3194. retVal.votesRefrain = row[2]
  3195. retVal.absences = row[3]
  3196. retVal.excused = row[4]
  3197. #suppress warning 'Attribute totalAbsences defined outside of init'
  3198. #pylint: disable-msg=W0201
  3199. retVal.totalAbsences = sum(row[3:])
  3200. #suppress warning 'Attribute totalPolls defined outside of init'
  3201. #pylint: disable-msg=W0201
  3202. retVal.totalPolls = sum(row)
  3203. retVal.dateStart = dateStart
  3204. retVal.dateEnd = dateEnd
  3205. return retVal
  3206. class MpVote(AutoSlug):
  3207. """
  3208. Class MpVote(AutoSlug)
  3209. MpVote stores a poll result for single MP. The attributes fetched
  3210. from database are as follows:
  3211. poll - ForeignKey referring to particular poll
  3212. person - ForeignKey referring to MP, who polled
  3213. result - single char, valid values are as follows:
  3214. A votes aye
  3215. N votes nay
  3216. Z refrain
  3217. M excused
  3218. A,X absence
  3219. """
  3220. poll = models.ForeignKey(Poll, db_index = True)
  3221. person = models.ForeignKey(Person, db_index = True)
  3222. result = models.CharField(_("Result"), max_length = 20)
  3223. objects = MpVoteManager()
  3224. class Meta:
  3225. """
  3226. See http://docs.djangoproject.com/en/dev/ref/models/options/
  3227. for details
  3228. """
  3229. #suppress class has no __init__ method
  3230. #pylint: disable-msg=W0232
  3231. #suppress too few public methods warning
  3232. #pylint: disable-msg=R0903
  3233. verbose_name = _("Vote")
  3234. verbose_name_plural = _("Votes")
  3235. unique_together = (('poll', 'person',),)
  3236. class Admin:
  3237. """
  3238. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  3239. for details
  3240. """
  3241. #suppress class has no __init__ method
  3242. #pylint: disable-msg=W0232
  3243. #suppress too few public methods warning
  3244. #pylint: disable-msg=R0903
  3245. pass
  3246. #suppress warning 'Argument differs from overridden method'
  3247. #pylint: disable-msg=W0221
  3248. def save(self):
  3249. """
  3250. Method save()
  3251. The saving a vote for particular person must update corresponding
  3252. VoteStats record. If there is no such record exists yet, then it
  3253. is created.
  3254. """
  3255. super(MpVote, self).save()
  3256. mpStats = None
  3257. try:
  3258. #suppress 'no objects member' warning
  3259. #pylint: disable-msg=E1101
  3260. mpStats = VoteStats.objects.get(
  3261. person = self.person,
  3262. term = self.poll.meeting.term
  3263. )
  3264. except ObjectDoesNotExist:
  3265. print "creating stats for: %s %s" % (
  3266. #suppress 'ForeignKey has no member name' warning
  3267. #pylint: disable-msg=E1101
  3268. self.person.name,
  3269. #suppress 'ForeignKey has no member surname' warning
  3270. #pylint: disable-msg=E1101
  3271. self.person.surname
  3272. )
  3273. mpStats = VoteStats()
  3274. mpStats.votesAye = 0
  3275. mpStats.votesNay = 0
  3276. mpStats.votesRefrain = 0
  3277. mpStats.excused = 0
  3278. mpStats.absences = 0
  3279. #suppress 'ForeignKey has no member meeting
  3280. #pylint: disable-msg=E1101
  3281. mpStats.term = self.poll.meeting.term
  3282. mpStats.person = self.person
  3283. if self.result == 'A':
  3284. mpStats.votesAye += 1
  3285. elif self.result == 'N':
  3286. mpStats.votesNay += 1
  3287. elif self.result == 'Z':
  3288. mpStats.votesRefrain += 1
  3289. elif self.result == 'M':
  3290. mpStats.excused += 1
  3291. else:
  3292. mpStats.absences += 1
  3293. mpStats.save()
  3294. return
  3295. def delete(self):
  3296. """
  3297. Method delete()
  3298. When vote is being deleted, then corresponding VoteStats record
  3299. must be also updated.
  3300. """
  3301. #suppress 'no objects member' warning
  3302. #pylint: disable-msg=E1101
  3303. mpStats = VoteStats.objects.get( person = self.person,
  3304. term = self.poll.meeting.term
  3305. )
  3306. super(MpVote, self).delete()
  3307. if self.result == 'A':
  3308. mpStats.votesAye -= 1
  3309. elif self.result == 'N':
  3310. mpStats.votesNay -= 1
  3311. elif self.result == 'Z':
  3312. mpStats.votesRefrain -= 1
  3313. elif self.result == 'M':
  3314. mpStats.excused -= 1
  3315. else:
  3316. mpStats.absences -= 1
  3317. mpStats.save()
  3318. return
  3319. def getResult(self):
  3320. """
  3321. Method getResult()
  3322. Returns poll result in human readable form.
  3323. """
  3324. if self.result == 'A':
  3325. return _(u'Pro')
  3326. elif self.result == 'N':
  3327. return _(u'Proti')
  3328. elif self.result == 'Z':
  3329. return _(u'Zdržel')
  3330. elif self.result == '0':
  3331. return _(u'Chybí')
  3332. else:
  3333. return _(u'Omluven')
  3334. @staticmethod
  3335. def get_presence_pie(dateStart, dateEnd = None):
  3336. """
  3337. Static method get_presence_pie(dateStart, dateEnd)
  3338. Renders a pie chart showing MP's presence for given <dateStart,
  3339. dateEnd> range. If no dateEnd is specified, then pie chart for
  3340. single day is rendered.
  3341. Returns GChart.Pie3D instance.
  3342. """
  3343. #suppress 'no objects member' warning
  3344. #pylint: disable-msg=E1101
  3345. vs = MpVote.objects.getDayStats(dateStart, dateEnd)
  3346. presences = vs.votesAye + vs.votesNay + vs.votesRefrain
  3347. presences = get_percents(presences, vs.totalPolls)
  3348. excused = get_percents(vs.excused, vs.totalPolls)
  3349. absences = get_percents(vs.absences, vs.totalPolls)
  3350. retVal = Pie3D([ presences, excused, absences ])
  3351. retVal.label( _(u'Přítomno') + ' %.2f%%' % presences,
  3352. _(u'Omluveno') + ' %.2f%%' % excused,
  3353. _(u'Chybí') + ' %.2f%%' % absences,
  3354. )
  3355. retVal.color('00dd00', 'dd7700', 'dd0000')
  3356. retVal.title(_(u'Nepřítomnost při hlasování'))
  3357. retVal.size(330, 80)
  3358. return retVal
  3359. class VoteStatsManager(models.Manager):
  3360. """
  3361. Class VoteStatsManager(models.Manager)
  3362. Class provides default manager for VoteStats class.
  3363. """
  3364. def getStatsForTerm(self, term = None):
  3365. """
  3366. Aggregates stats for all MPs in given term. If no term is passed
  3367. returns aggregation for all terms. Function returns VoteStat object
  3368. without any reference to particular person. Thus the returned VoteStat
  3369. object can not be saved.
  3370. """
  3371. retVal = None
  3372. cursor = None
  3373. if term == None:
  3374. select = """
  3375. SELECT SUM(votes_aye),
  3376. SUM(votes_nay),
  3377. SUM(votes_refrains),
  3378. SUM(absences),
  3379. SUM(excused),
  3380. SUM(absences + excused),
  3381. SUM(votes_aye + votes_nay + votes_refrains)
  3382. FROM psp_votestats;
  3383. """
  3384. else:
  3385. termId = term_to_id(term)
  3386. select = """
  3387. SELECT SUM(votes_aye),
  3388. SUM(votes_nay),
  3389. SUM(votes_refrains),
  3390. SUM(absences),
  3391. SUM(excused),
  3392. SUM(absences + excused),
  3393. SUM(votes_aye + votes_nay + votes_refrains)
  3394. FROM psp_votestats
  3395. WHERE term_id = %d;
  3396. """
  3397. select = select % termId
  3398. cursor = connection.cursor()
  3399. cursor.execute(select)
  3400. for row in cursor.fetchall()[:1]:
  3401. retVal = self.model(
  3402. votesAye = row[0],
  3403. votesNay = row[1],
  3404. votesRefrain = row[2],
  3405. absences = row[3],
  3406. excused = row[4]
  3407. )
  3408. retVal.totalAbsences = row[5]
  3409. retVal.presences = row[6]
  3410. retVal.term = term
  3411. return retVal
  3412. class VoteStats(models.Model):
  3413. """
  3414. Class VoteStats(models.Model)
  3415. Class encapsulates a DB table with poll results aggregation for particular
  3416. person and term. These attributes are being fetched from DB:
  3417. person - ForeignKey - referrence to Person instance
  3418. term - ForeignKey - referrence to TermOfOffice instance
  3419. votesAye - votes aye (result == 'A')
  3420. votesNay - votes nay (result == 'N')
  3421. votesRefrain - refrains (result == 'Z')
  3422. absences - result == 'X' or result == '0'
  3423. excused - result == 'M'
  3424. """
  3425. #suppress 'too many instance attributes' warning
  3426. #pylint: disable-msg=R0902
  3427. person = models.ForeignKey(Person, db_index = True)
  3428. term = models.ForeignKey(TermOfOffice)
  3429. votesAye = models.IntegerField(_("Votes For"), db_column = 'votes_aye')
  3430. votesNay = models.IntegerField(_("Votes Against"), db_column = 'votes_nay')
  3431. votesRefrain = models.IntegerField( _("Votes Refraiend"),
  3432. db_column = 'votes_refrains'
  3433. )
  3434. absences = models.IntegerField(_("Absences"))
  3435. excused = models.IntegerField(_("Excused Absences"))
  3436. dateStart = None
  3437. dateEnd = None
  3438. _votes = None
  3439. objects = VoteStatsManager()
  3440. class Meta:
  3441. """
  3442. See http://docs.djangoproject.com/en/dev/ref/models/options/
  3443. for details
  3444. """
  3445. #suppress class has no __init__ method
  3446. #pylint: disable-msg=W0232
  3447. #suppress too few public methods warning
  3448. #pylint: disable-msg=R0903
  3449. verbose_name = _("Aggregated stats for MP")
  3450. verbose_name_plural = _("Aggregated stats for MPs")
  3451. unique_together = (('person', 'term',),)
  3452. class Admin:
  3453. """
  3454. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  3455. for details
  3456. """
  3457. #suppress class has no __init__ method
  3458. #pylint: disable-msg=W0232
  3459. #suppress too few public methods warning
  3460. #pylint: disable-msg=R0903
  3461. pass
  3462. @property
  3463. def votes(self):
  3464. """
  3465. Property votes
  3466. returns a Votes instance for given stat.
  3467. """
  3468. if self._votes == None:
  3469. self._votes = Votes(
  3470. vAye = self.votesAye,
  3471. vNay = self.votesNay,
  3472. vRefrains = self.votesRefrain,
  3473. absences = self.absences,
  3474. excused = self.excused,
  3475. sDate = self.dateStart,
  3476. eDate = self.dateEnd
  3477. )
  3478. return self._votes
  3479. class MpDayStatsManager(models.Manager):
  3480. """
  3481. Class MpDayStatsManager(models.Manager)
  3482. Default manager for MpDayStats class.
  3483. """
  3484. #suppress 'method could be a function' refactor warning
  3485. #pylint: disable-msg=R0201
  3486. def createStatsForDay(self, date):
  3487. """
  3488. Method createStatsForDay(date)
  3489. Method computes a day stats for all MPs in given day.
  3490. Returns void
  3491. """
  3492. select = """
  3493. INSERT OR IGNORE INTO psp_mpdaystats (
  3494. person_id,
  3495. date,
  3496. votes_aye,
  3497. votes_nay,
  3498. votes_refrain,
  3499. absences,
  3500. excused
  3501. )
  3502. SELECT
  3503. id,
  3504. '%d-%02d-%02d',
  3505. aye,
  3506. nay,
  3507. refrains,
  3508. abs,
  3509. excs
  3510. FROM (
  3511. SELECT person.id AS id,
  3512. person.mpId AS mpId,
  3513. person.name AS name,
  3514. person.surname AS surname,
  3515. person.title AS title,
  3516. person.titleLast AS titleLast,
  3517. person.birthDate AS birthDate,
  3518. person.email AS email,
  3519. person.office AS office,
  3520. person.regionOffice AS regionOffice,
  3521. person.phone AS phone,
  3522. person.slug AS slug,
  3523. person.homePage AS homePage,
  3524. person.nasiPoliticiUrl AS nasiPoliticiUrl,
  3525. SUM(
  3526. CASE
  3527. WHEN vote.result = 'M' THEN 1
  3528. ELSE 0
  3529. END
  3530. ) AS abs,
  3531. SUM(
  3532. CASE
  3533. WHEN vote.result = '0' OR
  3534. vote.result = 'X' THEN 1
  3535. ELSE 0
  3536. END
  3537. ) AS excs,
  3538. SUM(
  3539. CASE
  3540. WHEN vote.result = 'N' THEN 1
  3541. ELSE 0
  3542. END
  3543. ) AS nay,
  3544. SUM (
  3545. CASE
  3546. WHEN vote.result = 'Z' THEN 1
  3547. ELSE 0
  3548. END
  3549. ) AS refrains,
  3550. SUM (
  3551. CASE
  3552. WHEN vote.result = 'A' THEN 1
  3553. ELSE 0
  3554. END
  3555. ) AS aye
  3556. FROM psp_poll AS poll
  3557. INNER JOIN psp_mpvote AS vote ON
  3558. poll.id = vote.poll_id
  3559. INNER JOIN psp_person AS person ON
  3560. vote.person_id = person.id
  3561. WHERE poll.date = '%d-%02d-%02d'
  3562. GROUP BY person.id
  3563. ) GROUP BY id;
  3564. """
  3565. select = select % ( date.year, date.month, date.day,
  3566. date.year, date.month, date.day
  3567. )
  3568. cursor = connection.cursor()
  3569. cursor.execute(select)
  3570. return
  3571. #suppress 'method could be a function' refactor warning
  3572. #pylint: disable-msg=R0201
  3573. def getStatsForRange(self, mp, dateStart, dateEnd = None):
  3574. """
  3575. Method getStatsForRange(mp, dateStart, dateEnd = None)
  3576. Method returns MpDayStats instance for given MP and date range (if both
  3577. arguments dateStart, dateEnd are specified). The arguments are as
  3578. follows:
  3579. mp - Person ID or person instance,
  3580. dateStart - datetime.date instance representing date instance
  3581. dateEnd - datetime.date instance if specified then
  3582. stats for <dateStart, dateEnd> range are
  3583. retreived. if no dateEnd is specified then
  3584. stats for single date are retrieved.
  3585. Returns agregated stats for single MP and chosen date range.
  3586. """
  3587. mpId = None
  3588. person = None
  3589. if type(mp) == type(0):
  3590. mpId = mp
  3591. #suppress 'no objects member' warning
  3592. #pylint: disable-msg=E1101
  3593. person = Person.objects.get(id = mpId)
  3594. else:
  3595. mpId = mp.id
  3596. person = mp
  3597. if dateEnd == None:
  3598. select = """
  3599. SELECT SUM(ds.votes_aye),
  3600. SUM(ds.votes_nay),
  3601. SUM(ds.votes_refrain),
  3602. SUM(ds.absences),
  3603. SUM(ds.excused)
  3604. FROM psp_mpdaystats AS ds
  3605. WHERE ds.person_id = %d AND ds.date = '%d-%02d-%02d';
  3606. """
  3607. select = select % ( mpId,
  3608. dateStart.year, dateStart.month, dateStart.day
  3609. )
  3610. else:
  3611. select = """
  3612. SELECT SUM(ds.votes_aye),
  3613. SUM(ds.votes_nay),
  3614. SUM(ds.votes_refrain),
  3615. SUM(ds.absences),
  3616. SUM(ds.excused)
  3617. FROM psp_mpdaystats AS ds
  3618. WHERE ds.person_id = %d AND
  3619. ds.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d';
  3620. """
  3621. select = select % ( mpId,
  3622. dateStart.year, dateStart.month, dateStart.day,
  3623. dateEnd.year, dateEnd.month, dateEnd.day
  3624. )
  3625. cursor = connection.cursor()
  3626. cursor.execute(select)
  3627. rows = cursor.fetchall()
  3628. if len(rows) != 1:
  3629. return None
  3630. retVal = MpDayStats()
  3631. row = rows[0]
  3632. retVal.votesAye = row[0]
  3633. retVal.votesNay = row[1]
  3634. retVal.refrains = row[2]
  3635. retVal.absences = row[3]
  3636. retVal.excused = row[4]
  3637. retVal.dateStart = dateStart
  3638. retVal.dateEnd = dateEnd
  3639. retVal.person = person
  3640. return retVal
  3641. class MpDayStats(models.Model):
  3642. """
  3643. Class MpDayStats(models.Model)
  3644. MpDayStats provides statistic aggregation for day and MP (Person).
  3645. These attributes are fetched from DB:
  3646. person - is ForeignKey referres to Person
  3647. date - datetime.date instance
  3648. votesAye - votes aye
  3649. votesNay - votes nay
  3650. refrains - refrains
  3651. absences - absences
  3652. excused - excused absences
  3653. """
  3654. #suppress 'too many instance attributes' warning
  3655. #pylint: disable-msg=R0902
  3656. person = models.ForeignKey(Person, db_index = True)
  3657. date = models.DateField(_("Date"))
  3658. votesAye = models.IntegerField(_("Votes For"), db_column = 'votes_aye')
  3659. votesNay = models.IntegerField(_("Votes For"), db_column = 'votes_nay')
  3660. refrains = models.IntegerField(_("Refrains"), db_column = 'votes_refrain')
  3661. absences = models.IntegerField(_("Absences"))
  3662. excused = models.IntegerField(_("Excused Absences"))
  3663. dateStart = None
  3664. dateEnd = None
  3665. _votes = None
  3666. objects = MpDayStatsManager()
  3667. class Meta:
  3668. """
  3669. See http://docs.djangoproject.com/en/dev/ref/models/options/
  3670. for details
  3671. """
  3672. #suppress class has no __init__ method
  3673. #pylint: disable-msg=W0232
  3674. #suppress too few public methods warning
  3675. #pylint: disable-msg=R0903
  3676. verbose_name = _("Aggregated stats for MP and day")
  3677. verbose_name_plural = _("Aggregated stats for MPs and day")
  3678. unique_together = (('person', 'date',),)
  3679. class Admin:
  3680. """
  3681. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  3682. for details
  3683. """
  3684. #suppress class has no __init__ method
  3685. #pylint: disable-msg=W0232
  3686. #suppress too few public methods warning
  3687. #pylint: disable-msg=R0903
  3688. pass
  3689. @property
  3690. def votes(self):
  3691. """
  3692. Property votes returns instance of Votes class.
  3693. """
  3694. if self._votes == None:
  3695. self._votes = Votes(
  3696. vAye = self.votesAye,
  3697. vNay = self.votesNay,
  3698. vRefrains = self.refrains,
  3699. absences = self.absences,
  3700. excused = self.excused,
  3701. sDate = self.dateStart,
  3702. eDate = self.dateEnd
  3703. )
  3704. return self._votes
  3705. class GroupStatsManager(models.Manager):
  3706. """
  3707. Class GroupStatsManager(models.Manager)
  3708. GroupStatsManager provides default manager for GroupStats class.
  3709. """
  3710. #suppress 'method could be a function' refactor warning
  3711. #pylint: disable-msg=R0201
  3712. def getStats(self, group, dateStart = None, dateEnd = None):
  3713. """
  3714. Method getStats(group, dateStart = None, dateEnd = None)
  3715. Method returns stats for particular group for complete term (each group
  3716. is bound to term by dafault). The arguments are as follows:
  3717. group - group id or Group instance
  3718. dateStart - if None, then stats for complete term are
  3719. retrievved, otherwise just for particular
  3720. day
  3721. dateEnd - if specified then stats for <dateStart, dateEnd>
  3722. range are retrieved.
  3723. Returns a GroupStats instance.
  3724. """
  3725. groupId = group_to_id(group)
  3726. #suppress 'no objects member' warning
  3727. #pylint: disable-msg=E1101
  3728. g = Group.objects.get(id = groupId)
  3729. retVal = GroupStats()
  3730. select = None
  3731. if dateStart == None and dateEnd == None:
  3732. select = """
  3733. SELECT SUM(
  3734. CASE
  3735. WHEN gs.result = 'A' THEN 1
  3736. ELSE 0
  3737. END
  3738. ) AS aye,
  3739. SUM(
  3740. CASE
  3741. WHEN gs.result = 'N' THEN 1
  3742. ELSE 0
  3743. END
  3744. ) AS nay,
  3745. SUM(
  3746. CASE
  3747. WHEN gs.result = 'Z' THEN 1
  3748. ELSE 0
  3749. END
  3750. ) AS refrains,
  3751. SUM(
  3752. CASE
  3753. WHEN gs.result NOT IN ('A', 'N', 'Z') THEN 1
  3754. ELSE 0
  3755. END
  3756. ) AS absences,
  3757. SUM(1) AS total
  3758. FROM psp_groupstats AS gs, psp_poll AS poll,
  3759. psp_meeting AS meeting
  3760. WHERE meeting.term_id = %d AND
  3761. poll.meeting_id = meeting.id AND
  3762. gs.group_id = %d AND gs.poll_id = poll.id;
  3763. """
  3764. select = select % (g.term.id, g.id)
  3765. elif dateStart != None and dateEnd == None:
  3766. select = """
  3767. SELECT SUM(
  3768. CASE
  3769. WHEN gs.result = 'A' THEN 1
  3770. ELSE 0
  3771. END
  3772. ) AS aye,
  3773. SUM(
  3774. CASE
  3775. WHEN gs.result = 'N' THEN 1
  3776. ELSE 0
  3777. END
  3778. ) AS nay,
  3779. SUM(
  3780. CASE
  3781. WHEN gs.result = 'Z' THEN 1
  3782. ELSE 0
  3783. END
  3784. ) AS refrains,
  3785. SUM(
  3786. CASE
  3787. WHEN gs.result NOT IN ('A', 'N', 'Z') THEN 1
  3788. ELSE 0
  3789. END
  3790. ) AS absences,
  3791. SUM(1) AS total
  3792. FROM psp_groupstats AS gs, psp_poll AS poll
  3793. WHERE poll.date = '%d-%02d-%02d' AND
  3794. gs.poll_id = poll.id AND gs.group_id = %d
  3795. """
  3796. select = select % ( dateStart.year, dateStart.month, dateStart.day,
  3797. groupId
  3798. )
  3799. elif dateStart != None and dateEnd != None:
  3800. select = """
  3801. SELECT SUM(
  3802. CASE
  3803. WHEN gs.result = 'A' THEN 1
  3804. ELSE 0
  3805. END
  3806. ) AS aye,
  3807. SUM(
  3808. CASE
  3809. WHEN gs.result = 'N' THEN 1
  3810. ELSE 0
  3811. END
  3812. ) AS nay,
  3813. SUM(
  3814. CASE
  3815. WHEN gs.result = 'Z' THEN 1
  3816. ELSE 0
  3817. END
  3818. ) AS refrains,
  3819. SUM(
  3820. CASE
  3821. WHEN gs.result NOT IN ('A', 'N', 'Z') THEN 1
  3822. ELSE 0
  3823. END
  3824. ) AS absences,
  3825. SUM(1) AS total
  3826. FROM psp_groupstats AS gs, psp_poll AS poll
  3827. WHERE poll.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d'
  3828. AND gs.poll_id = poll.id AND gs.group_id = %d
  3829. """
  3830. select = select % ( dateStart.year, dateStart.month, dateStart.day,
  3831. dateEnd.year, dateEnd.month, dateEnd.day,
  3832. g.id
  3833. )
  3834. else:
  3835. return None
  3836. cursor = connection.cursor()
  3837. cursor.execute(select)
  3838. rows = cursor.fetchall()
  3839. if len(rows) != 1:
  3840. return None
  3841. row = rows[0]
  3842. retVal.votesAye = row[0]
  3843. retVal.votesNay = row[1]
  3844. retVal.votesRefrain = row[2]
  3845. retVal.absences = row[3]
  3846. #suppress warning 'Attribute totalVotes defined outside of init'
  3847. #pylint: disable-msg=W0201
  3848. retVal.totalVotes = row[4]
  3849. retVal.dateStart = dateStart
  3850. retVal.dateEnd = dateEnd
  3851. return retVal
  3852. #suppress 'method could be a function' refactor warning
  3853. #pylint: disable-msg=R0201
  3854. def getAccordanceWithGroup(self, group, accordGroup, dateStart, dateEnd):
  3855. """
  3856. Method getAccordanceWithGroup(group, accordGroup, dateStart, dateEnd)
  3857. Method computes group accordance with accordGroup. Returns an instance
  3858. of Group (group), which is extended by these attributes:
  3859. accordance - an instance of Accordance class
  3860. accordGroup - the argument accordGroup
  3861. The arguments are as follows:
  3862. group - is ID of group or Group instance,
  3863. accordGroup - is ID of group or Group instance,
  3864. dateStart, dateEnd - define date range we want to gather
  3865. stats for. If both are None, then stats
  3866. for complete term are computed, if
  3867. dateStart only is defined, then method
  3868. will compute stats for particular single
  3869. 'dateStart' day. Both arguments are
  3870. datetime.date instances.
  3871. Returns an instance of Group class, which is group itself.
  3872. """
  3873. select = ''
  3874. groupId = group_to_id(group)
  3875. accordGroupId = group_to_id(accordGroup)
  3876. #suppress 'no objects member' warning
  3877. #pylint: disable-msg=E1101
  3878. retVal = Group.objects.get(id = groupId)
  3879. if dateStart == None and dateEnd == None:
  3880. select = """
  3881. SELECT SUM(
  3882. CASE
  3883. WHEN gs.result = accordGs.result THEN 1
  3884. ELSE 0
  3885. END
  3886. ) AS same,
  3887. SUM(
  3888. CASE
  3889. WHEN gs.result != accordGs.result THEN 1
  3890. ELSE 0
  3891. END
  3892. ) AS diff,
  3893. SUM(1) AS total
  3894. FROM psp_groupstats AS gs, psp_groupstats AS accordGs,
  3895. psp_meeting AS m, psp_poll AS poll
  3896. WHERE m.term_id = %d AND poll.meeting_id = m.id AND
  3897. gs.group_id = %d AND accordGs.group_id = %d AND
  3898. gs.poll_id = poll.id AND accordGs.poll_id = poll.id;
  3899. """
  3900. select = select % (retVal.term.id, groupId, accordGroupId)
  3901. elif dateStart != None and dateEnd == None:
  3902. select = """
  3903. SELECT SUM(
  3904. CASE
  3905. WHEN gs.result = accordGs.result THEN 1
  3906. ELSE 0
  3907. END
  3908. ) AS same,
  3909. SUM(
  3910. CASE
  3911. WHEN gs.result != accordGs.result THEN 1
  3912. ELSE 0
  3913. END
  3914. ) AS diff,
  3915. SUM(1) AS total
  3916. FROM psp_groupstats AS gs, psp_groupstats AS accordGs,
  3917. psp_poll AS poll
  3918. WHERE poll.date = '%d-%02d-%02d' AND
  3919. gs.group_id = %d AND accordGs.group_id = %d AND
  3920. gs.poll_id = poll.id AND accordGs.poll_id = poll.id;
  3921. """
  3922. select = select % ( dateStart.year, dateStart.month, dateStart.day,
  3923. groupId, accordGroupId
  3924. )
  3925. elif dateStart != None and dateEnd != None:
  3926. select = """
  3927. SELECT SUM(
  3928. CASE
  3929. WHEN gs.result = accordGs.result THEN 1
  3930. ELSE 0
  3931. END
  3932. ) AS same,
  3933. SUM(
  3934. CASE
  3935. WHEN gs.result != accordGs.result THEN 1
  3936. ELSE 0
  3937. END
  3938. ) AS diff,
  3939. SUM(1) AS total
  3940. FROM psp_groupstats AS gs, psp_groupstats AS accordGs,
  3941. psp_poll AS poll
  3942. WHERE poll.date BETWEEN '%d-%02d-%02d' AND '%d-%02d-%02d' AND
  3943. gs.group_id = %d AND accordGs.group_id = %d AND
  3944. gs.poll_id = poll.id AND accordGs.poll_id = poll.id;
  3945. """
  3946. select = select % ( dateStart.year, dateStart.month, dateStart.day,
  3947. dateEnd.year, dateEnd.month, dateEnd.day,
  3948. groupId, accordGroupId
  3949. )
  3950. else:
  3951. return None
  3952. cursor = connection.cursor()
  3953. cursor.execute(select)
  3954. rows = cursor.fetchall()
  3955. if len(rows) != 1:
  3956. return None
  3957. row = rows[0]
  3958. #suppress 'no objects member' warning
  3959. #pylint: disable-msg=E1101
  3960. retVal.accordanceGroup = Group.objects.get(id = accordGroupId)
  3961. retVal.accordance = Accordance(
  3962. same = row[0],
  3963. diff = row[1],
  3964. sDate = dateStart,
  3965. eDate = dateEnd
  3966. )
  3967. return retVal
  3968. #suppress 'method could be a function' refactor warning
  3969. #pylint: disable-msg=R0201
  3970. def createStats(self, poll):
  3971. """
  3972. Method createStats(poll)
  3973. Method createStats(poll) computes GroupStats for given poll and all
  3974. groups.
  3975. Returns None
  3976. """
  3977. pollId = poll_to_id(poll)
  3978. insert = """
  3979. INSERT OR IGNORE INTO psp_groupStats (
  3980. poll_id,
  3981. group_id,
  3982. aye,
  3983. nay,
  3984. refrains,
  3985. absences,
  3986. result
  3987. )
  3988. SELECT
  3989. poll.id,
  3990. membership.group_id,
  3991. SUM(
  3992. CASE
  3993. WHEN vote.result = 'A' THEN 1
  3994. ELSE 0
  3995. END
  3996. ) AS aye,
  3997. SUM(
  3998. CASE
  3999. WHEN vote.result = 'N' THEN 1
  4000. ELSE 0
  4001. END
  4002. ) AS nay,
  4003. SUM(
  4004. CASE
  4005. WHEN vote.result = 'Z' THEN 1
  4006. ELSE 0
  4007. END
  4008. ) AS refrains,
  4009. SUM(
  4010. CASE
  4011. WHEN vote.result IN ('0', 'X', 'M') THEN 1
  4012. ELSE 0
  4013. END
  4014. ) AS absences,
  4015. ' ' as result
  4016. FROM psp_poll AS poll
  4017. INNER JOIN psp_mpVote AS vote ON poll.id = vote.poll_id
  4018. INNER JOIN psp_membership AS membership ON
  4019. membership.person_id = vote.person_id
  4020. INNER JOIN psp_group AS groupa ON
  4021. membership.group_id = groupa.id
  4022. INNER JOIN psp_meeting AS meeting ON
  4023. meeting.term_id = groupa.term_id AND
  4024. poll.meeting_id = meeting.id
  4025. WHERE (
  4026. membership.post = 'člen' OR
  4027. membership.post = 'poslanec'
  4028. ) AND poll.id = %d
  4029. GROUP BY poll.id, membership.group_id
  4030. ;
  4031. """
  4032. insert = insert % pollId
  4033. cursor = connection.cursor()
  4034. cursor.execute(insert)
  4035. return
  4036. class GroupStats(models.Model):
  4037. """
  4038. Class GroupStats(models.Model)
  4039. GroupStats aggregates poll results for group and poll.
  4040. These attributes are fetched from database:
  4041. poll - ForeignKey, which referres to poll
  4042. group - ForeignKey, which referres to group
  4043. votesAye - number of Ayes in group for given poll
  4044. votesNay - number of Nays in group for given poll
  4045. votesRefrain - number of refrains in group for poll
  4046. absences - number of absences
  4047. result - if poll was supported or denied by group
  4048. """
  4049. #suppress 'too many instance attributes' warning
  4050. #pylint: disable-msg=R0902
  4051. poll = models.ForeignKey(Poll, db_index = True)
  4052. group = models.ForeignKey(Group, db_index = True)
  4053. votesAye = models.IntegerField(_("Group Aye"), db_column = 'aye')
  4054. votesNay = models.IntegerField(_("Group Nay"), db_column = 'nay')
  4055. votesRefrain = models.IntegerField( _("Group Refrains"),
  4056. db_column = 'refrains'
  4057. )
  4058. absences = models.IntegerField(_("Group Absences"))
  4059. result = models.CharField(_("Result"), max_length = 20)
  4060. dateStart = None
  4061. dateEnd = None
  4062. _votes = None
  4063. _accordance = None
  4064. objects = GroupStatsManager()
  4065. class Meta:
  4066. """
  4067. See http://docs.djangoproject.com/en/dev/ref/models/options/
  4068. for details
  4069. """
  4070. #suppress class has no __init__ method
  4071. #pylint: disable-msg=W0232
  4072. #suppress too few public methods warning
  4073. #pylint: disable-msg=R0903
  4074. verbose_name = _("Group Statistic")
  4075. verbose_name_plural = _("Group Statistics")
  4076. unique_together = (('poll', 'group',),)
  4077. class Admin:
  4078. """
  4079. See http://docs.djangoproject.com/en/dev/intro/tutorial02
  4080. for details
  4081. """
  4082. #suppress class has no __init__ method
  4083. #pylint: disable-msg=W0232
  4084. #suppress too few public methods warning
  4085. #pylint: disable-msg=R0903
  4086. pass
  4087. def getResult(self):
  4088. """
  4089. Method getResult()
  4090. Method converts the stats to result code, if result code is still not
  4091. defined (self.result == ' ' or self.result == '').
  4092. The result codes are same as for MpVote.result
  4093. A for
  4094. N against
  4095. Z refrain
  4096. 0 absent (no difference is being made between excused and absent
  4097. in case of GroupStats)
  4098. Returns result code
  4099. """
  4100. if self.result == ' ' or self.result == '':
  4101. #suppress redefining built-in max
  4102. #pylint: disable-msg=W0622
  4103. max = {}
  4104. max['value'] = 0
  4105. max['key'] = ''
  4106. for k in ( 'votesAye',
  4107. 'votesNay',
  4108. 'votesRefrain',
  4109. 'absences'
  4110. ):
  4111. if max['value'] < self.__getattribute__(k):
  4112. max['value'] = self.__getattribute__(k)
  4113. max['key'] = k
  4114. if max['key'] == 'votesAye':
  4115. self.result = 'A'
  4116. elif max['key'] == 'votesNay':
  4117. self.result = 'N'
  4118. elif max['key'] == 'votesRefrain':
  4119. self.result = 'Z'
  4120. else:
  4121. self.result = '0'
  4122. self.save()
  4123. if self.result == 'A':
  4124. return _(u'Pro')
  4125. elif self.result == 'N':
  4126. return _(u'Proti')
  4127. elif self.result == 'Z':
  4128. return _(u'Zdrželi')
  4129. else:
  4130. return _(u'Chyběli')
  4131. @property
  4132. def votes(self):
  4133. """
  4134. Property votes returns Votes instance
  4135. """
  4136. if self._votes == None:
  4137. self._votes = Votes(
  4138. vAye = self.votesAye,
  4139. vNay = self.votesNay,
  4140. vRefrains = self.votesRefrain,
  4141. absences = self.absences
  4142. )
  4143. return self._votes
  4144. def getResultChart(self):
  4145. """
  4146. Method getResultChart()
  4147. Method returns pie showing the result. Returns a pie chart for a group
  4148. stat. Pie chart shows percent of votes
  4149. for,
  4150. against,
  4151. reefrains,
  4152. absences
  4153. the default size is 400x80
  4154. Returns GChart.Pie3D instance
  4155. """
  4156. retVal = None
  4157. if self.votes.totalVotes != 0:
  4158. retVal = self.votes.getVotesChart(group = True)
  4159. retVal.size(400, 80)
  4160. return retVal
  4161. class SiteStateData(models.Model):
  4162. """
  4163. Class SiteStateData
  4164. class is a container for various run-time data for site.
  4165. These attributes are stored into DB:
  4166. recentUpdate - datetime.date - the last update date from psp.cz
  4167. recentMeeting - ForeignKey referring to last poll processed.
  4168. """
  4169. recentUpdate = models.DateField(_("Recent site update"), blank = True)
  4170. recentMeeting = models.ForeignKey(Meeting)