Parcourir la source

Merge branch 'weasyprint' of jocelyn/coin into master

opi il y a 8 ans
Parent
commit
fa90a889d1
5 fichiers modifiés avec 229 ajouts et 160 suppressions
  1. 1 1
      README.md
  2. 199 129
      coin/billing/templates/billing/invoice_pdf.html
  3. 25 25
      coin/billing/tests.py
  4. 3 3
      coin/html2pdf.py
  5. 1 2
      requirements.txt

+ 1 - 1
README.md

@@ -50,7 +50,7 @@ Install dependencies. On Debian, you will probably need the
 `python-dev`, `python-pip`, `libldap-dev`, `libpq-dev`, `libsasl2-dev`,
 `python-dev`, `python-pip`, `libldap-dev`, `libpq-dev`, `libsasl2-dev`,
 and `libjpeg-dev` packages.
 and `libjpeg-dev` packages.
 
 
-    sudo apt-get install python-dev python-pip libldap2-dev libpq-dev libsasl2-dev libjpeg-dev
+    sudo apt-get install python-dev python-pip libldap2-dev libpq-dev libsasl2-dev libjpeg-dev libxml2-dev libxslt1-dev
 
 
 You need a setuptools >= 18.5. If you don't meet that requirement (Debian Jessie does not), run:
 You need a setuptools >= 18.5. If you don't meet that requirement (Debian Jessie does not), run:
 
 

+ 199 - 129
coin/billing/templates/billing/invoice_pdf.html

@@ -1,135 +1,205 @@
 {% load static isptags %}
 {% load static isptags %}
 <html>
 <html>
-	<head>
-		<title>Facture N°{{ invoice.number }}</title>
-
-		<style>
-		    @page {
-		        size: a4 portrait;
-		        @frame header_frame {
-		            -pdf-frame-content: header_content;
-		            left: 50pt; width: 512pt; top: 50pt; height: 70pt;
-		        }
-		        @frame content_frame {
-		            left: 50pt; width: 512pt; top: 120pt; height: 632pt;
-		        }
-		        @frame footer_frame {
-		            -pdf-frame-content: footer_content;
-		            left: 50pt; width: 512pt; top: 772pt; height: 30pt;
-		        }
-		    }
-		    body {
-		    	font-size: 9pt;
-		    }
-		    #coordonnees_isp {
-		    	font-size:9pt;
-		    }
-		    #coordonnees_client {
-		    	vertical-align: top;
-		    }
-
-		    table#details {
-		    	width:100%;
-		    }
-		    th.cell {border:0px;}
-		    .cell.result {border:0px; font-weight: bold}
-		    .cell { padding:2pt; border:1px solid #DDD; }
-		    .cell.label { width:400pt;}
-		    .cell.quantity {width:50pt;}
-		    .cell.amount {width:50pt;}
-		    .cell.total {width:50pt;}
-
-		    .period {color:#888;}
-
-		    #paiements {
-		    	background-color:#EEE;
-		    	padding:5pt;
-		    	font-size:80%;
-		    }
-		    #page_number {
-		    	float:right;
-		    }
-		</style>
-
-	</head>
+<head>
+  <title>Facture N°{{ invoice.number }}</title>
+
+  <style>
+  @page {
+    margin: 0; padding: 40pt;
+  }
+
+  html {
+    box-sizing: border-box;
+  }
+  *, *:before, *:after {
+    box-sizing: inherit;
+  }
+
+  body {
+    font-size: 9pt;
+    font-family: sans-serif;
+    color: #111;
+    padding: 0;
+  }
+  a {
+    color: #111;
+    text-decoration: none;
+  }
+
+  p {
+    margin: 0;
+  }
+  p + p {
+    margin-top: 10pt;
+  }
+  table {
+    border-collapse: collapse;
+    width: 100%;
+    margin: 40pt 0;
+  }
+
+  h1 {
+    font-size: 12pt;
+  }
+
+  header {
+    margin: 0 0 60pt 0;
+  }
+
+  header .logo {
+    height: 35pt;
+    margin: 0 auto 20pt;
+  }
+
+  footer {
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+  }
+
+  footer .logo {
+    height: 20pt;
+  }
+
+  #coordonnees {}
+
+  #coordonnees td {
+    width: 50%;
+    vertical-align: top;
+  }
+
+  #details {}
+
+  #details th,
+  #details td {
+    padding: 5pt;
+    border:1px solid #ddd;
+  }
+  #details th.cell--empty,
+  #details td.cell--empty {border: 0;}
+
+  /* details cell layout */
+  .cell-label {width: 70%;}
+  .cell-quantity {width: 5%;}
+  .cell-amount {width: 10%;}
+  .cell-total {width: 15%;}
+
+  /* details cell style */
+  .cell-result {
+    font-weight: bold;
+  }
+  .cell-quantity {
+    text-align: center;
+  }
+  .cell--money {
+    text-align: right;
+  }
+
+  .cell-label p + p {
+    margin-top: 5pt;
+  }
+  .period {
+    color:#888;
+  }
+
+  #paiements {
+    margin-top: 40pt;
+    background-color: #f0f0f0;
+    padding: 10pt;
+    font-size: x-small;
+  }
+
+  footer {
+    font-size: xx-small;
+  }
+  .pagination {
+    float: right;
+  }
+  </style>
+</head>
 <body>
 <body>
-	<div id="header_content">
-		<table widht="100%">
-			<tr>
-				<td><img id="logo" src="{{ branding.logoURL }}" height="70" /></td>
-				<td><h1>Facture N°{{ invoice.number }}</h1>
-					Le {{ invoice.date }}</td>
-			</tr>
-		</table>
-	</div>
-	<div id="footer_content">
-		<hr />
-		<table widht="100%">
-			<tr>
-				<td width="50"><img id="logo" src="{{ branding.logoURL }}" height="20" /></td>
-				<td>{{ branding.shortname|upper }}, association loi de 1901 à but non lucratif - SIRET : {{ branding.registeredoffice.siret }}</td>
-				<td width="20"><pdf:pagenumber>
-					/<pdf:pagecount>
-				</td>
-			</tr>
-		</table>
-	</div>
-	<table>
-		<tr>
-			<td id="coordonnees_isp">
-				<p>
-                {% multiline_isp_addr branding %}
-				<p>{{ branding.email }}<br/>
-				<a href="{{ branding.website }}">{{ branding.website }}</a><br />
-				{{ branding.phone_number }}
-				</p>
-			</td>
-			<td id="coordonnees_client">
-				<strong>Facturé à :</strong><br/>
-                {% with member=invoice.member %}
-                    {{ member.last_name }} {{ member.first_name }}<br />
-                    {% if member.organization_name != "" %}{{ member.organization_name }}<br />{% endif %}
-                    {% if member.address %}{{member.address}}<br />{% endif %}
-                    {% if member.postal_code and member.city %}
-                        {{ member.postal_code }} {{ member.city }}
-                    {% endif %}
-                {% endwith %}
-			</td>
-		</tr>
-	</table>
-
-	<hr />
-	Facture N°{{ invoice.number }}
-
-	<table id="details" repeat="1">
-		<thead>
-			<tr>
-				<th class="cell label"></th>
-				<th class="cell quantity">Quantité</th>
-				<th class="cell amount">PU</th>
-				<th class="cell total">Total</th>
-			</tr>
-		</thead>
-		<tbody>
-			{% for detail in invoice.details.all %}
-			<tr>
-				<td class="cell label">{{ detail.label }}
-					{% if detail.period_from and detail.period_to %}<br/><span class="period">Pour la période du {{ detail.period_from }} au {{ detail.period_to }}{% endif %}</span></td>
-				<td class="cell quantity">{{ detail.quantity }}</td>
-				<td class="cell amount">{{ detail.amount }}€</td>
-				<td class="cell total">{{ detail.total }}€</td>
-			</tr>
-			{% endfor %}
-			<tr>
-				<td class="cell result"></td>
-				<td class="cell result total_ttc" colspan="2">Total TTC</td>
-				<td class="cell result invoice_amount">{{ invoice.amount }}€</td>
-			</tr>
-		</tbody>
-	</table>
-	<div id="paiements">
-        {% include "billing/payment_howto.html" %}
-	</div>
 
 
+  <header>
+    <img class="logo" src="{{ branding.logoURL }}" />
+    <h1>Facture N°{{ invoice.number }}</h1>
+    <p>Le {{ invoice.date }}</p>
+  </header>
+
+  <table id="coordonnees">
+    <tr>
+      <td id="coordonnees_isp">
+        <p>
+        {% multiline_isp_addr branding %}
+        </p>
+        <p>
+        <a href="mailto:{{ branding.email }}">{{ branding.email }}</a><br/>
+        <a href="{{ branding.website }}">{{ branding.website }}</a><br />
+        {{ branding.phone_number }}
+        </p>
+      </td>
+      <td id="coordonnees_client">
+        <p>
+        <strong>Facturé à :</strong><br/>
+        {% with member=invoice.member %}
+        {{ member.last_name }} {{ member.first_name }}<br />
+        {% if member.organization_name != "" %}{{ member.organization_name }}<br />{% endif %}
+        {% if member.address %}{{member.address}}<br />{% endif %}
+        {% if member.postal_code and member.city %}
+        {{ member.postal_code }} {{ member.city }}
+        {% endif %}
+        {% endwith %}
+        </p>
+      </td>
+    </tr>
+  </table>
+
+  <table id="details" repeat="1">
+    <thead>
+      <tr>
+        <th class="cell-label cell--empty"></th>
+        <th class="cell-quantity">Quantité</th>
+        <th class="cell-amount cell--money">PU</th>
+        <th class="cell-total cell--money">Total</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for detail in invoice.details.all %}
+      <tr>
+        <td class="cell-label">
+          <p>
+          {{ detail.label }}
+          {% if detail.offersubscription %}
+            <br/>
+            <span class="subscription">{{ detail.offersubscription.offer }}
+            {% if detail.offersubscription.offer.reference %} ({{ detail.offersubscription.get_subscription_reference }}){% endif %}
+            </span>
+          {% endif %}
+          </p>
+          {% if detail.period_from and detail.period_to %}
+          <p class="period">Pour la période du {{ detail.period_from }} au {{ detail.period_to }}</p>
+          {% endif %}
+        </td>
+        <td class="cell-quantity">{{ detail.quantity }}</td>
+        <td class="cell-amount cell--money">{{ detail.amount }}€</td>
+        <td class="cell-total cell--money">{{ detail.total }}€</td>
+      </tr>
+      {% endfor %}
+      <tr>
+        <td class="cell-result cell--empty"></td>
+        <td class="cell-result result-label" colspan="2">Total TTC</td>
+        <td class="cell-result result-total cell--money">{{ invoice.amount }}€</td>
+      </tr>
+    </tbody>
+  </table>
+
+  <div id="paiements">
+  {% include "billing/payment_howto.html" %}
+  </div>
+
+  <footer>
+    <img class="logo" src="{{ branding.logoURL }}" />
+    <p class="pagination"><pdf:pagenumber>/<pdf:pagecount></p>
+    <p>{{ branding.shortname|upper }}, association loi de 1901 à but non lucratif - SIRET : {{ branding.registeredoffice.siret }}</p>
+  </footer>
 </body>
 </body>
 </html>
 </html>

+ 25 - 25
coin/billing/tests.py

@@ -142,31 +142,31 @@ class BillingInvoiceCreationTests(TestCase):
 
 
 class BillingTests(TestCase):
 class BillingTests(TestCase):
 
 
-    # def test_download_invoice_pdf_return_a_pdf(self):
-    #     """
-    #     Test que le téléchargement d'une facture en format pdf retourne bien un
-    #     pdf
-    #     """
-    #     # Créé un membre
-    #     username = MemberTestsUtils.get_random_username()
-    #     member = Member(first_name='A', last_name='A',
-    #                     username=username)
-    #     member.set_password('1234')
-    #     member.save()
-
-    #     # Créé une facture
-    #     invoice = Invoice(member=member)
-    #     invoice.save()
-    #     invoice.validate()
-
-    #     # Se connect en tant que le membre
-    #     client = Client()
-    #     client.login(username=username, password='1234')
-    #     # Tente de télécharger la facture
-    #     response = client.get('/billing/invoice/%i/pdf' % invoice.id)
-    #     # Vérifie return code 200 et contient chaine %PDF-1.
-    #     self.assertContains(response, '%PDF-1.', status_code=200, html=False)
-    #     member.delete()
+    def test_download_invoice_pdf_return_a_pdf(self):
+        """
+        Test que le téléchargement d'une facture en format pdf retourne bien un
+        pdf
+        """
+        # Créé un membre
+        username = MemberTestsUtils.get_random_username()
+        member = Member(first_name='A', last_name='A',
+                        username=username)
+        member.set_password('1234')
+        member.save()
+
+        # Créé une facture
+        invoice = Invoice(member=member)
+        invoice.save()
+        invoice.validate()
+
+        # Se connect en tant que le membre
+        client = Client()
+        client.login(username=username, password='1234')
+        # Tente de télécharger la facture
+        response = client.get('/billing/invoice/%i/pdf' % invoice.id)
+        # Vérifie return code 200 et contient chaine %PDF-1.
+        self.assertContains(response, b'%PDF-1.', status_code=200, html=False)
+        member.delete()
 
 
     def test_that_only_owner_of_invoice_can_access_it(self):
     def test_that_only_owner_of_invoice_can_access_it(self):
         """
         """

+ 3 - 3
coin/html2pdf.py

@@ -3,12 +3,12 @@ from __future__ import unicode_literals
 
 
 import os
 import os
 import re
 import re
-from xhtml2pdf import pisa
 from tempfile import NamedTemporaryFile
 from tempfile import NamedTemporaryFile
 
 
 from django.conf import settings
 from django.conf import settings
 from django.template import loader, Context
 from django.template import loader, Context
 from django.core.files import File
 from django.core.files import File
+from weasyprint import HTML
 
 
 
 
 def link_callback(uri, rel):
 def link_callback(uri, rel):
@@ -51,12 +51,12 @@ def render_as_pdf(template, context):
     converti en PDF via le module xhtml2pdf.
     converti en PDF via le module xhtml2pdf.
     Renvoi un objet de type File
     Renvoi un objet de type File
     """
     """
-    
+
     template = loader.get_template(template)
     template = loader.get_template(template)
     html = template.render(Context(context))
     html = template.render(Context(context))
     file = NamedTemporaryFile()
     file = NamedTemporaryFile()
 
 
-    pisaStatus = pisa.CreatePDF(html, dest=file, link_callback=link_callback)
+    pisaStatus = HTML(string=html).write_pdf(file)
     file.flush()
     file.flush()
 
 
     return File(open(file.name))
     return File(open(file.name))

+ 1 - 2
requirements.txt

@@ -4,14 +4,13 @@ python-ldap==2.4.15
 wsgiref==0.1.2
 wsgiref==0.1.2
 python-dateutil==2.2
 python-dateutil==2.2
 django-autocomplete-light==2.0.7
 django-autocomplete-light==2.0.7
-reportlab==2.5
 django-activelink==0.4
 django-activelink==0.4
 html2text
 html2text
 django-polymorphic==0.6
 django-polymorphic==0.6
 django-sendfile==0.3.6
 django-sendfile==0.3.6
 django-localflavor==1.1
 django-localflavor==1.1
 -e git+https://code.ffdn.org/zorun/django-postgresql-netfields.git#egg=django-netfields
 -e git+https://code.ffdn.org/zorun/django-postgresql-netfields.git#egg=django-netfields
-xhtml2pdf==0.1b3
 -e git+https://github.com/jlaine/django-ldapdb@1c4f9f29e52176f4367a1dffec2ecd2e123e2e7a#egg=django-ldapdb
 -e git+https://github.com/jlaine/django-ldapdb@1c4f9f29e52176f4367a1dffec2ecd2e123e2e7a#egg=django-ldapdb
 feedparser
 feedparser
 six==1.10.0
 six==1.10.0
+WeasyPrint==0.31