Skip to content

Instantly share code, notes, and snippets.

@tomthorogood
Created April 5, 2021 15:06
Show Gist options
  • Save tomthorogood/a2429249266070b2f5bbbaebfc2c7778 to your computer and use it in GitHub Desktop.
Save tomthorogood/a2429249266070b2f5bbbaebfc2c7778 to your computer and use it in GitHub Desktop.
VCards in python+flask
# Simplistic example using send_file to send the contents generated in the service
@app.route("/vcard/<href_token>")
def get_person_vcard(request: requests.HTTPRequest, href_token: str):
vcard_stream = self.vcard_service.get_vcard(b64decode(href_token.encode('UTF-8')).decode('UTF-8'))
return send_file(vcard_stream, mimetype='text/vcard')
# I used pydantic to create models of the fields I was interestd in including in the vcard:
from enum import Enum
from typing import List, Optional
from pydantic import BaseModel
class VCardPhoneType(Enum):
text = "text"
voice = "voice"
fax = "fax"
cell = "cell"
pager = "pager"
textphone = "textphone" # TDD
class VCardPhone(BaseModel):
types: List[VCardPhoneType]
value: str
class VCard(BaseModel):
last_name: str
name_extras: List[str] = []
display_name: str
titles: List[str] = []
departments: List[str] = []
email: Optional[str]
phones: List[VCardPhone] = []
# I created a VCardService that is responsible for querying data and creating a model instance. (This is only a portion of
# the implementation, just to show the VCard parts:
from flask import render_template
from io import BytesIO
class VCardService:
@property
def request_is_authenticated(self):
session = self.injector.get(LocalProxy)
return bool(session.get("uwnetid"))
def get_vcard(self, href: str) -> BytesIO:
person = self.query_person(href) # Pseudocode; cutting contextual stuff unrelated to the problem
name_parts = list(reversed(person.display_name.split()))
vcard = VCard.construct(
last_name=name_parts.pop(0),
name_extras=name_parts,
display_name=person.display_name,
)
self.do_other_work(vcard, person) # Pseudocode
# Render the vcard template with the user's data,
content = render_template('vcard.vcf.jinja2', **vcard.dict())
# Remove all the extra lines that jinja2 leaves in there. (ugh.)
content = '\n'.join(filter(lambda line: bool(line.strip()), content.split('\n')))
# Create a file-like object to send to the client
file_ = BytesIO()
file_.write(content.encode('UTF-8'))
file_.seek(0)
return file_
{#
Do not adhere to conventional indendation in this file.
Line breaks for long lines must only happen _inside_ jinja blocks.
field must be on a single line, with no indents. This will
still leave lots of vacant lines that should be cleaned up.
Specification: https://tools.ietf.org/html/rfc6350
Model: husky_directory.models.vcard.VCard
#}
BEGIN:VCARD
{# woofington;dawg;husky #}
N:{{ last_name }};{% for e in name_extras %}{{ e }}{{
';' if not loop.last else '' }}
{% endfor %}
{# Husky Dawg Woofington #}
FN:{{ display_name }}
{% for title in titles %}
TITLE:{{ title }}
{% endfor %}
{% for dept in departments %}
ORG:{{ dept }}
{% endfor %}
{% if not email is blank %}
{# EMAIL;type=INTERNET;type=WORK:[email protected] #}
EMAIL;type=INTERNET;type=WORK:{{ email }}
{% endif %}
{% for phone in phones %}
{# TEL;type=voice,pager:5558675309 #}
TEL;type={% for pt in phone['types'] %}{ {{ pt }}{{
',' if not loop.last else '' }}:{{ phone['value'] }}
{% endfor %} {# pt in phone['types'] #}
{% endfor %} {# phone in phones #}
END:VCARD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment