pax_global_header00006660000000000000000000000064147047271310014520gustar00rootroot0000000000000052 comment=fa48979a8e6ece4bfdd439ae2bc37f2076ca6a65 django-solo-2.4.0/000077500000000000000000000000001470472713100137375ustar00rootroot00000000000000django-solo-2.4.0/.gitattributes000066400000000000000000000001021470472713100166230ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text=auto django-solo-2.4.0/.github/000077500000000000000000000000001470472713100152775ustar00rootroot00000000000000django-solo-2.4.0/.github/workflows/000077500000000000000000000000001470472713100173345ustar00rootroot00000000000000django-solo-2.4.0/.github/workflows/python.yml000066400000000000000000000010771470472713100214050ustar00rootroot00000000000000name: python on: [push, pull_request] jobs: tox: runs-on: ubuntu-latest strategy: matrix: python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install tox tox-gh-actions - name: Test with tox run: tox django-solo-2.4.0/.gitignore000066400000000000000000000001171470472713100157260ustar00rootroot00000000000000*.egg-info .tox .idea __pycache__ *.pyc venv dist files .mypy_cache .DS_STORE django-solo-2.4.0/CHANGES000066400000000000000000000100501470472713100147260ustar00rootroot00000000000000django-solo-2.4.0 ================= Date: 19 October, 2024 * Fix similarly named models from different apps having the same cache key * Drop support for Python 3.8 * Add support for Python 3.13 * Drop support for end of life Django 3.2 * Add support for Django 5.1 * Improve error handling in template tag django-solo-2.3.0 ================= Date: 1 July, 2024 * Add typing support * Deprecate `solo.models.get_cache` * Switch to `pyproject.toml` * Switch to Ruff for formatting and linting django-solo-2.2.0 ================= Date: 1 January, 2024 * Add support for Python 3.12 * Drop support for Python 3.7 * Add support for Django 5.0 * Drop support for end of life Django 4.0 and 4.1 django-solo-2.1.0 ================= Date: 25 May, 2023 * Add support for Django 4.1 and 4.2 * Drop support for end of life Django 2.2 * Add support for Python 3.11 * Drop support for end of life Python 3.6 django-solo-2.0.0 ================= Date: 10 December, 2021 * Add support for Django 4.0 * Drop support for end of life Django versions (only 2.2, 3.2, and 4.0 are supported). * Add GitHub Actions CI and tox django-solo-1.2.0 ================= Date: 29 September, 2021 * Make the necessary changes to make the library compatible with Django 3.2 * * * django-solo-1.1.5 ================= Date: 19 December, 2020 * Fix setup long description - for pypi page * * * django-solo-1.1.4 ================= Date: 19 December, 2020 * Now using the same README file github and pypi * Add pypi badge in Readme * Add a flag to allow not skipping the admin list page * Set the zip_safe option to False * refactor clear_cache into a classmethod * Added Spanish locale. * Missing import os added * update change_form for newer versions of Django * * * django-solo-1.1.3 ================= Date: 15 January, 2018 * Merge pull request #64 from the-vishal/patch-2 * Merge pull request #59 from hefting/fix-template-context * Retrieve app_label from opts in template context - Since Django 1.7 * Fixes #57 -- read SOLO_CACHE_PREFIX from main Django settings * Merge pull request #56 from girogiro/master * Fixed #54 -- wrong caching of FileField * Merge pull request #55 from jaimesanz/master * Fixed RemovedInDjango20Warning deprecation warning * Merge pull request #50 from m16a1/patch-1 * .delete() actually deletes now (#48) * Fix #45 add form_url to change_view (#46) * Added support for configuring the pk value of the singleton instance (#42) * * * django-solo-1.1.2 ================= Date: February 7, 2016 * Documentation update - Elaborated on usage of the template tag * #37: Update custom_urls in admin.py due to deprecation warning * #39: Deprecation warning in tests - `TEMPLATES` setting * * * django-solo-1.1.1 ================= Date: December 9, 2015 * Add apps module with a basic app config class. * Change the use of get_cache for Django 1.9. * Fixed warning messages showing up on Django 1.8. * Remove 'load url from future' tag from template (1.9). * Change the way `get_model` is imported. * * * django-solo-1.1.0 ================= Date: November 2, 2014 * Fixed warning messages showing up on Django 1.7. * No need to define a plural name anymore - Pull Request #16. * Fixed some inconsistent variable names. * Added 'load url from future' so templates work with Django 1.4 - Pull Request #14. * * * django-solo-1.0.5 ================= Date: April 27, 2014 * Pull Request #8: Python3 compatibility issue with `force_unicode` import statement. * * * django-solo-1.0.4 ================= Date: November 6, 2013 * Issue #4: Django 1.6 compatibility on `url` and `pattern` import path. * * * django-solo-1.0.3 ================= Date: September 14, 2013 * Fixed some packaging issues (licence file). * * * django-solo-1.0.2 ================= Date: September 14, 2013 * Fixed some packaging issues. * * * django-solo-1.0.1 ================= Date: September 14, 2013 * Added unit tests * Added support for @override_settings * Updated doc, licence and packaging * * * django-solo-1.0.0 ================= Date: July 11, 2013 * Fist release of django-solo. * * * django-solo-2.4.0/LICENSE000066400000000000000000000004061470472713100147440ustar00rootroot00000000000000Licence and Credits =================== This work was originally founded by [Trapeze][trapeze] and is licensed under a [Creative Commons Attribution 3.0 Unported][licence]. [licence]: http://creativecommons.org/licenses/by/3.0/ [trapeze]: http://trapeze.com/ django-solo-2.4.0/MANIFEST.in000066400000000000000000000002161470472713100154740ustar00rootroot00000000000000include *.md include LICENSE include CHANGES include solo/py.typed recursive-include solo/templates * recursive-include solo/locale *.mo *.po django-solo-2.4.0/README.md000066400000000000000000000175661470472713100152350ustar00rootroot00000000000000 Django Solo =========== +---------------------------+ | | | | | \ | Django Solo helps working with singletons: | /\ | database tables that only have one row. | >=)'> | Singletons are useful for things like global | \/ | settings that you want to edit from the admin | / | instead of having them in Django settings.py. | | | | +---------------------------+ Features -------- Solo helps you enforce instantiating only one instance of a model in django. * You define the model that will hold your singleton object. * django-solo gives helper parent class for your model and the admin classes. * You get an admin interface that's aware you only have one object. * You can retrieve the object from templates. * By enabling caching, the database is not queried intensively. Use Cases -------- Django Solo is also great for use with singleton objects that have a one to many relationship. Like the use case below where you have a 'Home Slider" that has many "Slides". * Global or default settings * An image slider that has many slides * A page section that has sub-sections * A team bio with many team members There are many cases where it makes sense for the parent in a one to many relationship to be limited to a single instance. Usage Example ```python # models.py from django.db import models from solo.models import SingletonModel class SiteConfiguration(SingletonModel): site_name = models.CharField(max_length=255, default='Site Name') maintenance_mode = models.BooleanField(default=False) def __str__(self): return "Site Configuration" class Meta: verbose_name = "Site Configuration" ``` ```python # admin.py from django.contrib import admin from solo.admin import SingletonModelAdmin from config.models import SiteConfiguration admin.site.register(SiteConfiguration, SingletonModelAdmin) ``` ```python # There is only one item in the table, you can get it this way: from .models import SiteConfiguration config = SiteConfiguration.objects.get() # get_solo will create the item if it does not already exist config = SiteConfiguration.get_solo() ``` In your model, note how you did not have to provide a `verbose_name_plural` field - That's because Django Solo uses the `verbose_name` instead. If you're changing an existing model (which already has some objects stored in the database) to a singleton model, you can explicitly provide the id of the row in the database for django-solo to use. This can be done by setting `singleton_instance_id` property on the model: ```python class SiteConfiguration(SingletonModel): singleton_instance_id = 24 # (...) ``` Installation ------------ This application requires Django 3.2, 4.0, or 4.1. * Install the package using `pip install django-solo` * Add ``solo`` or ``solo.apps.SoloAppConfig`` to your ``INSTALLED_APPS`` setting. This is how you run tests: ./manage.py test solo --settings=solo.tests.settings And from within `tox`: ``` python -m pip install tox tox ``` Supported Languages ------------------- - English - Spanish - German Admin ----- The standard Django admin does not fit well when working with singleton, for instance, if you need some global site settings to be edited in the admin. Django Solo provides a modified admin for that. ![django-solo admin](https://raw.github.com/lazybird/django-solo/master/docs/images/django-solo-admin.jpg "django-solo admin") * In the admin home page where all applications are listed, we have a `config` application that holds a singleton model for site configuration. * The configuration object can only be changed, there's no link for "add" (1). * The link to the configuration page (2) directly goes to the form page - no need for an intermediary object list page, since there's only one object. * The edit page has a modified breadcrumb (3) to avoid linking to the intermediary object list page. * From the edit page, we cannot delete the object (4) nor can we add a new one (5). If you wish to disable the skipping of the object list page, and have the default breadcrumbs, you should set `SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE` to `False` in your settings. Availability from templates --------------------------- The singleton object can be retrieved from template by giving the Django model dotted path: ```django {% get_solo 'app_label.ModelName' as my_config %} ``` Example: ```django {% load solo_tags %} {% get_solo 'config.SiteConfiguration' as site_config %} {{ site_config.site_name }} {{ site_config.maintenance_mode }} ``` If you're extending a template, be sure to use the tag in the proper scope. Right: ```django {% extends "index.html" %} {% load solo_tags %} {% block content %} {% get_solo 'config.SiteConfiguration' as site_config %} {{ site_config.site_name }} {% endblock content %} ``` Wrong: ```django {% extends "index.html" %} {% load solo_tags %} {% get_solo 'config.SiteConfiguration' as site_config %} {% block content %} {{ site_config.site_name }} {% endblock content %} ``` Caching ------- By default caching is disabled: every time `get_solo` retrieves the singleton object, there will be a database query. You can enable caching to only query the database when initially retrieving the object. The cache will also be updated when updates are made from the admin. The cache timeout is controlled via the `SOLO_CACHE_TIMEOUT` settings. The cache backend to be used is controlled via the `SOLO_CACHE` settings. Settings -------- ### Template tag name You can retrieve your singleton object in templates using the `get_solo` template tag. You can change the name `get_solo` using the `GET_SOLO_TEMPLATE_TAG_NAME` setting. ```python GET_SOLO_TEMPLATE_TAG_NAME = 'get_config' ``` ### Admin override flag By default, the admin is overridden. But if you wish to keep the object list page (e.g. to customize actions), you can set the `SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE` to `False`. ```python SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE = True ``` ### Cache backend Django provides a way to define multiple cache backends with the `CACHES` settings. If you want the singleton object to be cached separately, you could define the `CACHES` and the `SOLO_CACHE` settings like this: ```python CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', }, 'local': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', }, } SOLO_CACHE = 'local' ``` Caching will be disabled if set to `None`. ### Cache timeout The cache timeout in seconds. ```python SOLO_CACHE_TIMEOUT = 60*5 # 5 mins ``` ### Cache prefix The prefix to use for the cache key. ```python SOLO_CACHE_PREFIX = 'solo' ``` Getting the code ================ The code is hosted at https://github.com/lazybird/django-solo/ Check out the latest development version anonymously with: $ git clone git://github.com/lazybird/django-solo.git You can install the package in the "editable" mode like this: pip uninstall django-solo # just in case... pip install -e git+https://github.com/lazybird/django-solo.git#egg=django-solo You can also install a specific branch: pip install -e git+https://github.com/lazybird/django-solo.git@my-branch#egg=django-solo The package is now installed in your project and you can find the code. To run the unit tests: pip install tox tox ### Making a release 1. Update [`solo/__init__.py`](solo/__init__.py) `version` 2. Update [`CHANGES`](./CHANGES) 3. Make a new release on GitHub 4. Upload release to PyPI ```shell tox -e build ``` ```shell tox -e upload ``` django-solo-2.4.0/docs/000077500000000000000000000000001470472713100146675ustar00rootroot00000000000000django-solo-2.4.0/docs/images/000077500000000000000000000000001470472713100161345ustar00rootroot00000000000000django-solo-2.4.0/docs/images/django-solo-admin.jpg000066400000000000000000003527561470472713100221620ustar00rootroot00000000000000JFIFCCi" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?d%cMDžzþYw6ψ45?D[Ǩج!QM3!Ψd8(fthTP/Ƴ"دcBj. ٶ&tc<|[h6YĚ{D-އBh! ~k9!)bvHVI#+\Iùl~$ϲ\VXl҆U`s n!ε5CK ^uL=zj"4ByyGfJ8lN/WakУ$*sՄa^Ik@k~$դSi:< $1,K&9c3__a_WcZExD.! m.Fd]OL4Y HL36_\/c5fW:Y>o/5fy~0WJx3GdUyrոc1xSZ2w[Gf8L&; qb5zW JS^ѯRW'ki_qPNnt<(|mmmv\v]{/?ҤuJ}1|q{?ֵ_/.K Eմ[m*P5kMǭiiyIWWie$G8 E0rLѫ'Ǝ2T(W5IƴѫQAœ%t),5u1T_ Me+*T+SNr[S[$pqFgBXLxxb:O EN]\L*5NT!V3\<ފϊ~+|m #NM>֬u}cb]'>uj2; b)2 ]jЌBSZ0c:rR$M2W*ূ3/^+׼]xo|%6qx_ e᳧i}r^ePI~>$Fx?֧k۽?G1xO;ol|kޗx@KMgjb3t<8c>?[/⸊OJhC :yL4R:2LVe(JXq5`|N=Xөhb#ɇςSǗPC__mFLxQŎ-k2Q3kzci5͚kmYHm0WCn>Hiڎkv~,."TR{hVW~ 緼%91ʼV3|ʧpOS:2Sg&_0Q(},ƤUbibTF 0XϰTafa1U0UqX<6)օ Sur)QHANqzF)>S#-߉ iZEX6v+imLO{i6Wпg?>"j< ݼSŲ,ڥִukՂ+hdjo-N8j+%2ކ}_>i.w`2B>(c10aac^/Ps"fR,e,Tˣƥ<%|ug*q:Pt0jQhaFLGxzUkS+Ҿ%!H& ғ\a'[B?uMoÚͼwI麭ѶL7)0k2/Ͱt3̲TL6;/`0RbөB(9Se^~' ֞AAΆ&JB5iҫ*)¤R82MQEvQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~ƶEoyIcX]Whmֳ/me-X꺝Aoa_]ҢmEJRiF1rb)7RmINRb)()7)JM()j&IjIjeZ:NkizjS6W:s&|{+8vCRH8SRC\h.[{K}o_Lӯ/X,5}b[XeM3KPt/Z Ei0#8'$9$a'%I7ŸM);&+?u٨N\h?w܍?z>4QEQ!EPEZkԲQ{;.n,kyV{H溵蠂[ho-%$iaIVxU>VjWuhm4_Ľ2O:hk;g6,ѼuNүGK Kbm Kmbb]Xg^nh֡x+<|W:M?ҞmrmCJ_qaE~W88y&&gC+OʳZY',˸Ώ#ʳc,vEBT>"y6BT3 RaO 9&!U<=/yv#벣)N f/$)N?ُ*sOL)@_X|Ko4:ᛝ+XKl[1h?xoƟ1|M38Msh .&Dh@+|GpUqY چGYw,6K, -OFwk81(eE8bQ)OYG8gRceVx߬PZK/ Y~_\Nt%W~BZtS|s>|g#W ~7<3Z>(f][Gì0~ ϟ#jGi/_ jg-}"K^ֵ P|Ci]z]Z¢+m'\?jX|;&3p;~W,nf-iw>s$V~b1#!Lz]źx௄;/j|9Ԭ9Ukoߌ jHx  a5E|~ATr/*L WÌ3,2?qsJX21\q4jcpS`R\pW,DBX̨?p")WTy)8(q O|tA >#xS߉|-;Zj}&sgRC$ÞƧVOyٿZo[Ho/^5gWZOY'K+(ʬ++2|\1(0\7]TBWO7dcͱ5W'LS60مig5g͆#̲LLr/ȩabWN;NbQHVA–axX%({w~?t `.ռl𕶪6$_,igoJ+9[(ο8WgPRh`8/~:8W9N#dP61iRq;a*`J\/)hcxN*Krت*Ts/Ruh׎%渾zp _?jKx߲w-W76_/HO\-]U?SQ Icqf< bݭE$A4KvFA_*͏1UtTςxw,2gԱ_$x ASYe)Ԍ,Xa!KU6؜F!e?ί/GJ,bINT_~YvSL,/4^V-m#gbXQ #@@Y5ݯuEmq4»+ԊFE m8R|or)V_c O?X|1N^# ӦS#IK |c[,,pc,5e9<[;{}ZN^jK{[ڿ٥cǑhXf?yK7_2G|nbOTQ_pYn//XWqf~ogqFoĿT~ڿ7^k?o{?g0<}L4?f YG5SF!ʥq?{()E0+(((((((((((((((((((((3k/泭xC~!~Ͼ$/~ ic|2X+xF/#IdlXJU5i|5ƏnWGSh^+O ꖖzWĿ MZ#Ƌ{sʶd e իRpҴpWTµNTʆ#,NcNjԣZU9x\|Bam} i'N?>ZoH?/]L8_4mcg!N*xZ<,%Nt-#c*b)QSK4_O.%]u\𱓄IN9P1X汊*O)pASFž4=K_ZׂtoZxRhxR]Ϗ%>xZnɼ/`9\B3 ij\im?howSS.}MCB귞,Ƥ"ѯ-gBїa Btg4G jKK4 8ON SXʕOFӔ>}ZͰEF/%PA<1h.g/lu+a'~IxkkC_O ^PxoT5g:>D{IbJJ&;# 8R~ch8M N)ԜcqX\ʦ'apgPTՆ TTePXTXJ+BP~r*,?d=;0US"wgl=.yQS}CsQJd%%yr)ca Fy{:t[Y~~,M  ~>a|c^et~B GqBR[,Y6[˹&Wď{E3e_{zFp4/N')布\M:3,/>d%EEKnWe 0ԫӨޥ j1xYՔJ gUisJ![8lj %שzӢK*Dn9NST5%u,uZwu''O/g˯EM 43<'&qUλm#QKNҼG姇"³IVB9O6>( /?ig~1>h:7qm?[h~]׼/tokUtf[[Iӭ&l'_*ఙN7eԔ:+jPZai|#6\8Ztg]]zF_|V( gX.UM}]|5Ú~ ǏCk]/N~|sD<+kMXkkxz_u=WI6 Znowq^_fK~ſ_ Vf i>G<-xgc&d7Zxz}n/Cۭ?e[tc)NyҦ 2|,}W4Tdѭ/i5:F=xG0 yG*6*Ne{,CxwN5*PxKğ> xRdkoy#\æ|I~=_0MWǗvOxtVΕz}K:uLJ_>1f?j=8| i />:h>= 3uua^j4.|y%qYF'w_߃χď^M5/¯x7x|:зӵ ~x+<0{w__A-UP5;GOpO/"tپO}|W>4Y|+_> 3yi~~ ºE:Jnnoejԗ[֦Ѭ96K;R5?BPFkm:UljZW^̲Eel*ySxHb%Uʽӭ[ƄdQnZթb'VEJJyʯ6F8R~jO|,]|4(ч 4_|f6YuS#?x;Ǻ.;>F'_C_RĝNjt5Fi/~xm}}?l~'?eK%xrx#Ou :;x=RͦE{E (iUXpy<.+_?}S*O.2TuPZNT MJ6-ZTⱔ0tX`0*X'`O;M}־6?>7)gៈc5 BE4[> /Hj>5!_IoxWY7]G k]սk⯅|ӵ_/o7v?|'Ӽ#-g4o x>. 1}{zmOdE1&#a<3T!1ԭGYSQS ԣW#UWOk]Y, Zu1X|R&|Zx*4NeF9,NhC11T,'η~񖻩߳VK _h_OEkm-5: ֽkD)Ȟ'/F/jUh:MSQ) |[|Gzfu/~%X麞F-\澆?Z)ɡ[ ε&XVF2gFXZ]F03*rUe48 V :k,<ҝ[֫S  u18X/C3ͩF4)G诀K~.I'íG~xȗ:LpT|-xv.|WISU&"W~0|w9}wgat-h}b r.Ag<7xm̈́]\Mu ]6?WI-m|a^5wrwhuЮ$y(bdEaɌʫ(q18G 18<ҩRui^7  ^ /n936K/af)ժ fITiў7RrTX#OǚeF>Wf».@^W -o?Q'%v~z38>/gW4Eٓ7z~$}Oq7o'T|J4ɼ1F]7W𞓨5Z~p]0xUiEҌe/{ SJXI:VAr|P ,4*Fo>eɊ RⱴԒ1|Ջg/ ? ,x6 _CA6X_>' ﭭe藷h׿ 伖@֚ GE9~9}cAJjJMIԕ4#%NfYJJ(NRNn'Rk쟽%5N3[q__OstÂlO)?O?E~+ a xG|woGbInpOE'i9"'<#>q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G# ??XQ7G8'4cG]Q aB8-#][؟Rf,~(bIn( !OϜÂlO)?O? ??XQ7_uA?XA? gW~VD?'٧ ?؟Rf,~(뺊? ,? S?+"?OstÂlO)?O?Ex~?)?ߕpOE'i9??'٧ ?q[?G_OAxog'3[o>F/᭷|zFaxW7WTP\q]]SOo׊Oc+['A]? ~ k4cGK?b}=Qxcq< 퀹䯂v=UQ"+:p(J7]QN'ZQJjF9cWMՎOΚe%*B2J~ƥhWpq&AE_|imඹ};q|+G. SZm ]{ƚ͸ņq..u4hn( my#ߊ?h?׿|GK\ÝJow^%',xt ì=:}\Zmo?KSlZs]?Nu1;_iˢg!o Ğ,JKg(G|xgþ|w]OQt+WK{ZE R,6zTہMd#jG1Ü)9RtІ'ebhbzx0&l:VCR4igABS˨{W<V6:*O7~rԫ BJm`IC RjUQ=:u/;OҴhu{OB>(]v^K>wjer*S'5gN흹rڝF:Zu<_hi]<6oCO_']j;5nH~oOgeռEBƇg>k4J+M2 nV/7[b(V1u4*th' Txz2^ӌZgB'ä*C SFY,KTRiSjս*J?߉O )x_ uٴs>eT-ZK_ vVM-Ioc%3ވҧࣺ>H?ONH~ zo|>7zo-@X =n4)na3~k|Uo?`?%Ɲk?W>.v6jáKZ{լdү k>Qht9.l`)' w~?xVu-?@\| '-|sih6KkQXxič{&MI-tiӧ8 V*u0׎FJ\j9(SQM8S'kpcQTV:bnc*sN6F4/R]:QR_;~? a>=xc z!}IlK="H<#5/ӭkϮ%xx3)~$i/7kZ ,&_д_j烡_i bH' ?,gx'V(jž\Z7iv"lu[B_iwvڄEw.sucNwV+ xo^J=ޓesi-k6T̂;iGк+ \*yK*fz9q hn1q+)):rFTNq=9R*[QY'":n2 2"n6SINT0($~? #ſ ?kO~ZGkf,g{IGHP43?VEk-*x%@-{_L7ſG{gYqZ[+y .<3=soK_,-~l iW׆#a>aLNآ#E訊!o@Gu?[5mYꖐGi_iw_}$4FBpsYӖQUҒZ/ujT(qNqw)_O %:jyԱ  4c(̕(IS8#:r|JβJV9a*h/O_KďϿ4oAX4X .cV?K͜l ͦ4k!}$¿37^F ɠOƱMK*>FVqs|xR+H'M>qm'ugi?WHM"I",b']sN+XU!c]O s/1H˦z׾$x?}xׂ᷄Qճ*Uu9 J4V/u!ST$w6uz>Cz }.{)]@1JySvSF^' n:Q15^__'jO?R<` )֋Zq J X'B:4pR#QاᏌ~ ?M'1]"C= Qugs$q$ŷ<#>xhxh!%~?an;_!^|/ZwZ>fwũ[+ e;XFA*x4~7.Ƈi/[Sej_t+;{g]]إF."9;?ⷉt_(6oF8F?k= ;Bt W:h$ԴORFQn3S/X,hR4ScMʤuKFZሧ55SV7<ac%M*Ӕ`#FKEB4(I*T9c σeO!gςk?HGoxJS*Oѡ x.md)u74oߴ/t Vxbī^tqjw%i )S D|Qjrx~ xOİǪx<@i7MuA[ğ~Z^ÿ_C*W3A_LK%ܲ'H%Ӛ^(7Uf~ Ju9x[,ΰTUIB8r|~6ArxJU1r`QX*<2sBKʱu#VECB7(TSb(TJ)Ix!7/c~(77| sgz6kai=k績Au4γkY]_\Ɩw(]ƶ|xKז-3AԴOx:Y]AힽxcIcѮo7(^$gW|Tkᆉx+ּg? zT𥟀t2k贻$TEԵM?P9WA$7Ɵ_ n_w?a߄!<}T'?>]i?^Ky,%u Ү=bkZJsha^:tNjx|ˇXNoSjgNtS֝|du!b*ys'{!{jRsLF :)T=(G iOH?b>2t წ>2u/ @[K{4z<|"o௎4:M:c5f=Λ͖ڌQ\[\5Y][[\,߲w<1,?4 #^D6u}>PK GP{\wqs\]XI2([nn&G?궾tp!>;Y[(&l.1[ńl5@/ײzAdiB)eU8MN\Ui5QYn]ze*KXN#yVp3*58UrMJ 6) (((((((((((((((((((((((((((((((((((((0ƚkcm?ZYvֶ%m# x"HGQF@G,"x|4Bp*֔HΝ6N˕+&seF:%.iBsMΚ9^nikyE~Y~0ʇԼ\42Pv 㬟e^ߎۨW ɉˤ' (֮fi%utG.*EMsAʕx̼E~K_Tw|iպ>+[ie[)c īw%[ql4*&2ydߗM\COANxK_v/'U- K]u/*?\p/*o:w_`n!~xP[rxi?ZoZ^:_Uk_LOZ^:+U??ԬG]?ܷ}c?^O֪+[#׌x>?:(㮟 x;~N(Rm:|0[rxi?ZhaKGgUx8] {~y?7̡~_◎zZ'׷\f{:'y?7V~Q_u׌{uJ?ix/*uO;f^:ܨo?44W-/gGt]^x9>:Ih|o&2V/NԚ3xEi pIyM̳!ZOfRj zI|Rʸ \6xz.7 ^5&kMZE}aw]ͤuCߌON_=.Ã*Jx:Ҕ'G (-J2N4h\G0\V1bժaz)RBʝj5TF*FTSc8N.2JIewWx_UkV/5_qc~_ueV?N K_=x tcXGo=[-XZ'~/?ǎ ?ԬmO-X㮟 x;~N(tzZ珷Gqn[>/'M- K\w/*O>)xU={uhR[_`n#|m3i%ixSK KY?]x wQpVlq ‡ۖϬg IE~K_Tw#ߌծnи+6{VG_0[rxi?ZhoZ^:C{_.'-/qߌ}#{Vk?7Pa~Q_uCߌON_=.uCߌծz?/5q ۖϬg IE~Kox ta=:)?ix= wy_?ܷ}c?^O֪+[zZ8&_Ukf+6o-Xvl?/+:gZ?w|i:{o ] - PHDƿl+3R$e} _4 . jDk%ўMZDo,,]sڍYRSM}+&iR*R̖Cc1TRqU,Mj\&8C Tۼ:|q++*fYF;2rYMLGNU:[9γ],Gpe9|60խD_g_.[A̿ ϋ=uN_`n#Cm3i%ix= wy@OǾ#Xgo/N-sh/6So/h^A/4M4iMsE({5t[+7\|Xof{3({Ixj?%/8?暐`3S:*پIMBKmO⟍lS,u]x!stmg {ʰ6 BM F9%V|QD$ǩqy$v8TDPYUPI i6\J14J1JRz+E;oFm$nKwvҲݦi=#S7G? &>}2]sK-J!OS|gd[=N.!Iy#U t>V|OKn-[◍>K*C\6Љ&8"52RN6N7QkZiJ _(j/{W5e:IŭM=Q4W)8G1xWP@Mcy&oG x'gdg*r8Nӌ.ߒc8z23({Ixj?%/8?暀=G x''^2Éi({Ixj?%/8?暀=G x''^2Éi({Ixj?%/8?暀=G x''^2Éi({Ixj?%/8?暀=G x''^2Éi(K77UWw^̲Oqsq>g,,M+4#4Hʼn'(((.k| ojIݏ6,>8$WE~V|@~+P|s{VZ&?}ZռEmj=`h.$ʽu }bX,ֽ\v"UjUmFU^' 8Y7wME/ŸLV3-KB"ӔJѧ11jJQWZJƗſxVgGޓ!tw= u n,_-‘ZSi 7 WYI0ZuTE# ~V|OJ>u<[lzQ w_l })("x6SW V*k gjÞ/s9dU%<-[q.u\hٷ"Mmen#][4xǂ>,鷚ρ._x 18ڶwpk~ۻ]jw(%O[K3i:sO{nxR 8 xݲo :xcZiƦU^MTi8>6O~ >⟥ψ[{N?ß08%\<;L,$'<1ø<\b*i5?le_wxOh{ ]+} 'M|f_-mx6/x&OH75W2Mg<9i #DڕˠU:`J>ק#G0?A3O->D^MeYs^#s19axɬR2(ex ,Aԝ|_}-|~l| dxY*m^MOlΔc*q|Ya15ώ15hen.Lޖ"ha9i>*>!xōs6<'xKQğE47vit6Olr&' sl:nqWsgyngPp0uZ8XBºi)ߓm> ˳\rn5Ͱ.-c^>͸|G–<ʃU2 rz<,E|6qْx|~ 5k;b>խO^h$? jWvVj,橣ЛkG^t7MфukYj6|BUĩ 8!l9>2hx{Zx75V{xþ) xmº/]kT{E^[\[hcLK 2x'ѼAd5oA;ë-/]j/ǫ.7izmhӋ._q9m'gUay{36 ^b0:,>Yb*cC x/do_:~%뇱,nwM~+Iynu>^q_Ox|vUɨ g> OAOq8=#s Go"O#t*>ƾ{4lo]{?}>vYdx/$`Lzz:L'$Cyt3|yv9=qO@!:O% ]o1Լ yߥгC<0wמ1}9S'$Cx#_~9{CGO<[ۿ<8$v$P`f:n?QÚ/l~_ K-~//Kx'$crxOׯ_A1~zg:twysOPz~tU ۾.Ic?xr6ԯm?moU/[x/$c\1\@F8̝88=><޿:sO{nx$c:s 8{?i\x#sżg()>:|]<Ni,ml=6 >Vlxo/Kx$C?"_h?C7ZxgĞ-R!?dW_ w6h=[S;_ |{+|>q'e]_[(p1͸n."aV)e<U,67W.L-:4ε_ _ <OC+ |w>/9E ¸,gÌO<1ӄ(edl9U|V;Vy8z~;gJ'|tsmyox@>xcHjZ_x~1MBjB}- ;}+#|+Q/_~^Wx q4ks3ha0L>^Q:ɼF+;8xMjrNI<&_naWT3l96Re(_X\0O,Jj+$c#01 >cק=k#|+?? FWgz~axh&q%v>0ow5! d=?'P6~_g>-^>8|YE|Yux_ uTִ9B}8kwk݉"`q.*O! ӡ&x>=eOT5RVIujW7wK3w3:>6+ 'e+e Q*̲5NaV*V!_{4]Ꮙ|+/hu c]csp|+<1xLRؚ}  `~5ǀ>,k ߈2:ŃO?OVMZF},f .U~/U VʺsZ3bМ_*jsJe:Z:cC:rS/:-T1SWG ]LR7Og~ZQNx3x_-?_W}V] ԿώnX`噒n3CK+),×6тT :<=_kSúSa5}B_~x_u?VHMXܾ5sk&qs ϓg Vʺ?U ?W[{HMlDSj'~1Vx*Q4i'Jt",V*ժFc:Js:81X=z 9YOoR-oeI:QpS_o oMŋ Hh7|o`u-OD7zh:y~Ɨ!~.|!ᯆZӼg|B񭵮}_Wz߉tKB.5 ,0XkQ).c/*Z*W?+P]sNj`sNjIb|B_˱P$rR:R\V&q8t**.PB8FUs nc#pTJ>Es~|]1~*s^㿃5kGx]ڏ|;Xǐ4jqb[Y/cs mŋm^ sמ!񞓩G'4h?꺍ׇ7ow+yZI2~«U~/Uֵe:골UؼF' ~ε =j0PX`0SN5,aP$(b']Rl> xvNrS Zj˚gƹ)~"x]~P  #4??jI^4񭽑O iH5?GEVmW_|u_4Ֆ:oڰXa #Cٿ~4W?+P]_?jGT{ߙc1w$,1,sE*>KV/괲jiKH2c j6^??j |B_JK7}sF)d?*Z*W?+P]j^&TqgESWcs*\Ia(:(*R QwFʕJUF۔  RM&x?oVuG+/g*(?_@Ct«4#~gh~gjOU~/U VʺlQlQ?U ?WG*Z*?=GƏ=GƤW?+P]_?j?_@Ct«#~gh~gjOU~/U VʺlQlQ?U ?WG*Z*><vu|8y?+J ëoo]Z}8<2|[mgK)~govƫ_K6>#{h0IZX]quz\E3]Z˸ZLXf/tiIm|,ҡըJ8F/QpUQ-旲&ӥb[B8fl-*䴱T}F C Uuk`V sz>q_k ?-Cx>ǍE?X^xcK{FHU/l`cuho` ytո(>/8x&| K^_U^]Ow5:H_xZOǾ >s;] _O&.W5ki}N]~:_g>F)'>%-?,>"4{]c?i~%h?x??l?_=ХF 'ma,MgXx_ȳl-~:\ >+~_Un}c+- IO[kr&_϶eK8J+|M>*w~_Iy>[k [Zzm&+kI.u [ǭ]i6??nك^!~%KwH}]o| D\Ӽ1gukRx^]su&~W -cx v񧅿iڏmhOgxP?uxLz֚\u ޝ_kjY"|!?i q [⏎xW~%xG=^&M:~V9OOMwOv6,:zea1jὝ\1ut:4x[t^q E)QIa*BέJeWFoԎУ,gaAźwl|GύCSTּikh+5?C&c^%D_ Z%i:Λ"DW_0 A f:g$WtY b$E#P vg%)|#ߴ~|W¿ڻ_<7uxX𽮻mW_5)wʚl5>g7po w P?,s?g_yo][xmdo|+X`V "lӍ<|I1rZpΦEue,F3|L2J]l;ZJt+QEa>QE6~ m>8/׍3~V>W߈~ZKu8>9<xKyA[xUdu5Kn[T#ӄjtZZU} FTPѪTaU=(5<3UVl31u`qt s,1gYsL0V˳<>eZ.ͳ\׌6оO oH{_ Eω8|K|,f: O|L\~M{:.Y֞.W}=> |,uc> ߁|5m!Vt i6ER"yq ^ϵMܳLtX\7aBxz0ԩU  T"LCS*T;&^}<[qN ͸fxl537/#Ss\}|F7 -x&QOZqc԰2(>L(((((((((5,߱jfɟ*WFjLJ~/?[3}|]QE}YQEW x[Qo&K.^ +(Un&ӴGXX__/wK> #Z k6 ]7X{tDN¹xF7#N{FO .o]izdz!x']x[Ǿ9{T=~Aum^Χ}2Tvi{h7V:Fƣ}[Z iz_to KC մsi}儷hYy/ڠ>z_OkX4/h|^2]x|3]2<+xz0ЇZ? /W/-Qg?—+?f~$$w_]ҵjn_iMmIw4JGsuwK,,q+RSaWꪵk<r8Y*kGS Qb5VR+2h,9<%[Pi)#jx O,4FtRU>(5xzx>^1ѤյKD-2)i{+֛7ڏa]f(4BN+6կ Ta? uAŇğK~ Ӽ?ǃ-YthA_mjl>d6s5fGW~#4/C< *kkƯ]xKR(WkGϏo|kxoPֵ/Içj^iqS%NyYja0/\N*5Ea)b1e,EnLlQkK24!trFJX_ag_Ƭ* z*^1\\[!Ӭe [IX$H!~-) ~c^4vգWg5 tCZmf?<ƒ81mP9ӽJkn[ֆ#3Tihf5WJæb_gj+RuSN4=U5^MҴ2 .KQK +h+{h#iHј~cH^Z>$CmQw?e5ϋkqӬI'b-`tX++-s5k_kz?}A)ǪDrG.%Γm^$Qx!4"* L6F|F2]N*O cq9V13K׳r8}zڥ[ >L- 7gRYa& c9~x+Ǟ_:<}[n7ExXIi{ ^_E-pܻG$2CY N3~?mٿO8|Ԭ?|v9G?~ _<+sTt+}CĐ9qXaz2> 6Zz7Sg7g? ?jڋ㯁>~?GK{-wN=sZ74MC@ GIKn_AVK~^ʭ)NKFSN8 &2srT0,V*uiO\T$]E1Յ:q)e VaNNXcpG W ~/=ݷ5y~#[{VmMTtvs.m&\\mg>_^>OÿgeO-r&>#|&>7W&#_[x-<}.TѦկ#ъmվhs@|RO)#J4`ߌ~6o~#xz?~:^/SwXt0h#^A-с/laY.l63TS^RX G/c+΄} ԣZ8ӗ#F i:IJ^HFYrƼӫB8FU0lTgVE>_׌3;WCE|lK3+3|{^%м3&k~X^^U+s&}}^M='? Q/)j>񶻮L_>4KzrxĿ>WV1h:SI&&yOeШuΣ_jdXEUJ1QWW],;rC+*\+a改sj:P8Mt)ʔ@QEbnQEQEQEQEQEQEQEq?x ƻ:><vtQEQEQEWi|;/íkTxi>{=kŚֵe4pqn!J]W@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%_/4[/%~"‹WQ@w o /IG%2W/( %z%Onl} !]Z?*w>Eo>chrnU_JE8e(|OZ]^.'y/ViM+Z^_[]Oc{A}r>dhOud 2,+sv'a3jyd8t0U00T*aҫKIs'NrMIJ2wϼ?qFO\Oe :b=*FiN:߻"_JE+^撿7+7&??+7&?$??yӯ7y7C"F"_JE+^撿7+7&??+7&??_aH|cQo?H-E_i(_JEud ud ~(C3w:w<++9o/K|eD_QxJ?-E_i+wAY*cAY*c_?]N< G>_3+^撏K|eD_QxJPJ.hPJ.h$??yӯ7yC"F"_JE+^撿7+7&??+7&??_aH|cQo?H-E_i+ےo??|57eCUo5osm Qhڶ2Dy$4|{*PJ.k;V@O:e|w|O!7ZO7|mZmxCŚ-Inu RuYcbq*1(picRyœeINje'9K2Q||>ppb>58w< 'fxL®,FcJk׆TʵZtUIEԩ^KOLW,LOx6s x{ǁ#~o_ xcE>ɣ{úmvk/66I$]͖7*F)=oUfhAqk7)8Cޣ*XYXTgO )S7VQMN t¤R<2x/|}Ex9 <#kygItx5vhmVd5IRF~ۮ~u5̚ƣoiieZϡCV=[j7Oϭ{|znŪߝVS=.m6 %*<Ӝ)ErB)ԮT5jө9Ib+NiujrwZ9TR3%ni˖'v:P#e 4Nm@Ѣβծ'5-JF; ش4b}?I};JmVg/u/#KK 6I>k+F>oyF"_5п'.wV)"zj?8t%kG:5£F-?%W__a= "zj?8?'.wV)=B[O?xTá-']<*?n(jܿ"_5п'.wV)"zj?8t%kG:5£F-?%W__a= "zj?8?'.wV)=B[O?xTá-']<*?n(jܿ"_5п'.wV)"zj?8t%kG:5£F-?%W__a;/.]CeW`m>) b%T7_efs'|~eDc ^6?Q3qEWh1\:M}?o6SQx5V~W?&pW Ϋ"f b G/2G9o6Qᶷ?[-Wgs"g_n?&o./* $|T2G~vw_З0p^v'<:M}z!3Ƣ1մx1\QBnzD ݙ)o? 󲧧'3㖲<`B2s#ޟ s{iiOBf 7kS 2G[/?B|eGD}'_vU7:+_\5!?.q? /hCZo">|D;*?#-O!> 󲩿^a- p?斏_,@N~c̤?'U9-d@C_01ȢԄ$iâ5E\Z~ l 79+5:_ۥ'?"wҧe0"-O_'>GDU¿`+{-^#?Ljg.O@?+CJo?DU¿`(?Woã<#EG-^#?L'?,D _7Y_"*Us_0E_Z?ߊN|+ў?#]`gs"wүe hv?9,DzPآZFQGaL/ck_$w3x9+m%A>u6 {?Z_tw? }#T>&_Y՟C:o7oK?mmveWG,Gx#/?eGK 033V~W!o-$ 7?!o-$ 7%YQG_/ʏa?wgs"ηY_/ck<Yv%I$2NxUU@peWG,a~0xĜrGP 퓏SGK 0?'o ?\?oKD|w 5GD|w 5^E?OG:C?E/,O~;V~W /Ij /Ij;!?"t+1D?>&*_Y?c_ QY_ /,$x_Ce?P a קä>_'$P?4 }|ҏ ahP~;V# =7# =7/Vq}%׎?O4.%:?Y03V~Ww;DGw;DG򢽛%׎?O4äZ & a`Pb??+? I ?QG? I ?Q^mEk'?_ xGĿG 0?(po 1A?՟ZO/W2Th EpTQc ,ZRHёՇcT_XOH3ܿ^I/|rWOҦJ|-!q/Q7,x;1/ޗ~!Lw/"D?i.Zt*?OU<:%+"v'1టg ~!Lw/"׿å>TxwJ|-!/P¼?,Q/V~W~!Lw/"Su?Sc(TxwTO /<%l$QQGĿ A<A؟ ޗ{~_t9G? IrK|#xgTåO~<3ʪ6 _՟E.Wb_gWHUO[Oګ+ot?)džUG:[?A_]KK7WY_~7)Uo+$Sç[-oUQGE?7*PxWCR +V~W-Ud~*ZpG0.f+d*_Wc_U'_'%8Kh1=_t) =Gğ ºR ??_W/Hw?erzc1gG¿Q3E#w_gP8WGE>_b(_WcG> Ix5:cׅ1gG¿Qq'a$?1A?+Fk=^1V[nJ?ࣟ"w9w ]b灂91gG¿S 3TgEcdLuwxjLcx?$|?"^-܎#oKyGlH/=i~ٟ_}_cæD^Zt(?+?Sğ t_CC_?+sGlH/=i~ٟ_}_cæD^Zt(?+?Q? S  >{ I? #|=fA|G}#ExGj?_|T-GC$o+aO?+5dwO). ~+Z'&;ol*\*>+ I#&~/Y_moXe=R1=yq?~/GC$o+zENg/>+ I#&~/YG>+ I#&~/Y_oæo^2t-Sğ tguS?'5g_VI3{?_VI3{{7#2w1æo^2I>0WO~W?*p >+ I#&~/YTVf)7Ԭȋc mGpv"lO鿁?|[W ;(㯌  E<>r8V<@?Oº7T c{[OwXt{[OwXtNpWO"F7jO= I',~:sG= I',~:s_{çg~1_yGC#8_+AW?#5DUgeDVwv 31UTYHfO_}:A\[>~wl?\a 2޼OkIqs迲o|T:Wu^~"i>~kSAu?B5-G[6okkhڨ+kyɒWs<?X|n!ߊ' f^^77!NH$c@?֗<hg.7PW+$e~5;&Tv&Xe)??mfJI[,|1n;vѬ!z'F1'-׾\V2GK621q~>Hl֗c9Y@_C 8zؙcXtW Qfq̸-1P C=,'*r3TY9B.4BtW녯W ,uom~~/DMύ4 cFHmPOw]MjI%~zgį/~-|2e蚥xoB߈> GO/Aa F6q]tj¦]-~4ćxZ,VxЭ*N>8IᰙXqnUar'iz21?\0ʸ帜=L,s|+0͜ӫUPp/0[R5tJtJ*ӫψ~_R}?ڟmk<xᯂ<#j?!H,]nc6fOh]˨Mw-kks}umeg7wB$7,0Aj <h;+cjg̻4po˧>fqTqI{ǒ8>*HRrq>NkX|Žaཽ:5St'KBҝkTF:aV?>+>Mx/ /[!`֦m>8JPMv[o;US⺺[Թ5@:Gxs| _)8[>,ǂ|qkHeJ姌<5/j~ 6.jWVqhuαiw vI.GǪ\AK9^&1$R҅L_qna˸yq'S$rfgp*oz9mluJTSp-L2-gX\f&}BS'\TeQW'x/+ōFDÿ|oc++5)fմmw7KQiIZ,R9 >4<-

:Oj-ѼkE=[WBJĞ3O k>oc&y*/~.pI~J&+-Upu 3¼U,mo_% [ J*n{thΎ.8 UJ&9t0T5bkөU(J`ҝ:xmJi<=j.q_aQE}IQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@_Do6|-_b^XCckw /izb{!o:k]O<9ῂ ռM&#e uO]Mu%焼-_h[i_ٯeY.1`S'Y(QRR4}w9b"BjpԌ#&*p!Rd SBUF*jsQu!8SRNT>j nXHUtU\D@tv}|IōW/w}|<6|9/c_7ժr\g8%j!}_Qq84p_ %^~%5x4:)Ū:Xùsҧ *ʵZN8|5 quNcCMh<=,xռWoWj+7y; 3(b=ĸ;Vn=TqPB\T9\Zn oNc7FSK]QmUH+O߆&1> \_ 7,?XxA[_o|]eWoZZ0i:ǖvVquyq O<\H  M,H 쨠L5fS.,M:N8{UNЕZtX8TjNtev6ԒV4ez*>iӓm'yI&Ph"$"Y|jQ$~6|k[Y|!u|]YAk$6#&KM_ > hz#I㟈كS7[i|3yu ~xÚݮ厵 ZKjZRpwE >V=6 _SXYUx;Wή<>C N~N,T8G8x0Tは)Xh*hQjta *M乍%FܩT͊ *5KoSj]/vK/ixͤV?s[KN.[{|G lZO 7[ [cAt_-bkUg=wLd݋V=r%fO V9&Tԩ,5,>'RZ4RQ9`ጣ9i.X: Sp*2urxu\Mu Օ,6.2E}wD~:-'|T/ơ-6xRu߆CfU{wpWvu>nky៉?$e{ oč_D/FៀzmΡ ͍wv51W`s2˱ `5aXjeT)BNTENrN^#(s .Y^#O b0Ug*1r0jjʽ8^dT(> xx_Y;τy|`cόUbN_w XtwL5Ԍi%&f+c[#}|Cv ~)G㋯;=7_>>xO߆폊tZ .[ؾm4jƎ&ӝ ucÖ%S:S8ʼTnu£+RJ|d=:sWTtg U0|QBTW^!|+ F}h_ Y[f=>%5HxvɴBRŚq%ڿG_W^ּoC{Zc:>usHvexQޮgmk`U%RkSPg*?wr8s,zR:U!c_Բ<jIRb)¶7YJ8J_NiUN09ʵS*+?I~|joAxǞ ޷|+l54[-bM&&^n R־Gm"["]y__/*)WI~#jZOc񞩫x~ CM;xnn-j?Yki} ^44Y՞[!c.sͲnq8TR]NhNKaZjRh*eZ:xW8VѩwNhVaF~ג:|=jjrV9ih[I돂g=3ⷉ>cV|=γڿM.+Qd/H|3xTy4$B}G_ x:᷻7O];=+m>6߁h. ;ZTkh}F{s+]燼WJ|$~:/س|.+<)jDj5P:tä {{HX|xJ5ft,q*~cJ5=+wK0IR1b99FU2.TRP}o(#{Þ,G_=~5P<69/nwxÚ79V֭i<Njtk[M6{다S #ŭGOwkwz$Á-o׿!sxNVZ_4<\xė~"Ӵ!agRU%ic+aaJt:KV/aGZ^%Iɱ𫉤JQ(ף bcNV(Ѩ狕(q5uv:q콡Tԭ~/|'qimet#;*+V 2i\$`E%Kwֿ߄Ucōc4I0?u 5eŖ7G^'my}&'Ӣ%x%VK vybTPS PԭR.κd'jb+aaB/CJXWΕ*8e?z:ZpGÏt u|M-fOx/M֮|3~Җ綹> Vk-G%ޏ_O#?-^~ALvH;Y%XݗeQQ#KFlM nnxZ(nVzjQՄ[TNp`|MUҊNUeT'(¥ e ŵtܡ5ĽZVKl׺FeZ-孽]i1]VWQkyle1I[7O:g ߿|/eoqğ |-Nuu OV^(N5}WIm3hBkI cm#vqב`xk񫊲|5aVtp1FXʊ*TY/F5<SAܔ SJ7)J Ʒ䵩YsiT*UpuaP SJrr=>F gUF88K;\(cPŬ5S+>eVwwl?x~74/]NYWg4nRK5YҴb4ִ if/"ݼ ᷎o~A?ot'֥\%u;]3R/n-"CmIk+<&HApG 8x rܻ42%| a2i!9llC6xᯈAܔ SJ3L3ᄆg 7~-iKUBt)ҞX|vK0J|'Fyab1~uxqS^qxTii{jYI:9aBjiƥ v.ԩb*}k_kY /b \v5vt^kΩ]IWx/㟋| \4;÷7 ㏇(R淰Ѽ}ik7:[jrvYDdڌڥ14uez]>^Aܔ SJd[/x-aXQ_ K2N(x#PmfXͻ5fRQ[Tj֧^*kq;Wì&t0Xl,cB")Ҍa%MŏX~ e|A 5%Z߇=_@]I4ҥn]&k⾂$}.m@7σ|};\:DÅ.Lj@t;nB 4Ia_ApG 8x 䮬^]6XpX59ܥf9 *2{[8:J<,'x }-?{ `98W ݝ ҤZMt'|GVyh/ /:GnkuKo xsSjVl޶e%xb.+Aܔ SJ`+ }*O0Orxעө .aI/e{S?Zt(VTќ(Av%Vq(^ҩ9Mɝ SJ?_P%v|yF6y!gѿ8woݹɏVh>]o3]Kr@ 'ٯnmQ]ry$Y_Sùx"<+Rg6Y)#)s\ ~ N U5%IY|R7|+ Fxi|]w*V~ Ç3,F]WG^7-S RiNU(қ"QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEw >'׎t/? u<1_ Iy&Gcf:uޓzV:yΝwk,70s1eߏo|y'JOod|,Oy)uAEs;wov&0XGWЫWV|$}Jre{P^J\7-bTa*tq8TyҭR%5~hBJ/a*n)Aߩ?MrĆFiI |-֣485cBoU"5[M"A'b];V- ,^x_I/#EO.:Nu+KK+(-|^96QJ1ʲƂ¥ESz$%MB'8(YFxV̳xlhՅg,Uy:Q nqcV24Tou?iOާ$4]#z.g_<[_ <tt_ ɮxBOK6'H]Q.t:k^Z}:?e/%<-ſ Ф;|H, EzS~|;>Фm?xU&&Gkmec'L*cbKUgs<>"]ǧh&m#Lf0:CYK -s]^\ݴqV2`j7<=t\ g5J1SipRjS{{WIPZcFZ¥:4N:)Ql[xQӵN"[ 1j>,ݥ/?|K}k:\ +VBQRH䷺%k[fX<> UaujV T}JF.Jb5g;ʦ#7*J\UqXqJRJ0T*pJ6i+HE/tԿieGx7vOXcG_l~!xf0{jZφ)NYEN >?|U֙hznhZOo<kk ? #}Ki: Vb"5;SLfAkk >7E5$u*$*((r^-F,V"Ʃͣ.ycqrxWr6w,.MYaIJxşcⱫ|N9u-K/u]'F wWtEV {Ap浭>#7_-Wח t 5-߯&_t9k9U^ %xVW׷׷k={FFQH.$S㖵B/RV7Ȯuk]WҴ&񿂼[{ucĺ^.?%O,g+xL4ZVlm .iKni'u)_5ƪ+UyUՍl4TOcV2ƣbԡe^1BESqWVywGTT{WG:t?/<#W7,.gҼ: Ҵ6Vmq19i~ׅƻS s|A#5{k7Cm}mgior-/i´: <=&B9QiZR0"-j4,4?ɬUtXTK'5'ӽZëyRRj;a{Xko|9o[_Vէ5? 𖁯xGW3s{y{˩oǏ_Cl+x{ntixºtNiz=|4 "XO6IktU.M9MUuXQK꣯`0U/υ*rǑcqj5}ª\vk Xe r:^^5>jZƣ}^Ɩ1&R|E> o~4֍[ %xNZ(ay j<נީ}n!|g\kZ4ד/ Jӵ%E4wM-B+[ۨe ( MJ4)Q'NPrQRqRSj;7O,EY8UVsn*+iZO;]VNĝO_u[NOx-A),f|'*獼/ W?5 OLk=JO53kKIyw6BsQK .z4iђC T#>xz1mSqRKE_֩YεlDVr)r{zӜ۔Upsrjݶ.6+o/lum3\пg/tnO/(ZxXlhj^ K? ]Eş ZDxN_~ #I>cNGȔVkycA*jtiqƺ B1 bUcsΗr<ʒX~VwI՜jqGIRԜNWh?`>37xo~ KAM z7 II.:um&Y&"iG,OU~ӿu?WmMSxO}~c  i '~پ,]7ċM V0ќ#SQtVP*RT)rM,:Luoc:aI+JUi66MEAI=7R^𼩤xᅎ/υ)|}{Oዟx!/~뛋M-nlg{/$,JH#;"8T$ĉ.TEkOBrF)KIӧ9)U^I7^jҾkUo*Mt_^Tj֩Q)ƍ,\ 4J细/;IrA"(OWO&;N?<;OΓoK}F_5x WմGú@н)f2xCw.b{ ojOZ [ O./0x-m<+ K]R|A6c$2]K'4hgr^' _0VrxocOЧ*IUbN9:JL] =b1P a}9ըoh*9Bqc<&3iJ'qkvlԿ'dr Wľu}rE>x[ vA&8~!@Nw6}wcK X_Qލ fV/?C6e*B/Fzޘ5MC\{7fư_Mk32cETR5.Tr9է/~QEcJ}mӡ,E:Uf8?ʊt'*v\BL-?q,ʵh{ya[<Յj_2O9_{ouϝky^f^fqye'/ρ%W4o9xjQ_V}W@g+->]H mVvZf4&Ⱥf5c_3q_-- jku4m=$U_ٺY=QO K5 L1ԣ9#/g۔*I{:թFRt i>TgSl<~j5y͒Y:tfM5gok_ $YZ:Տǟ_ w>'oy3\K-.-!I*M}lK>]oSgk2o xfK_zC}dòKվ)j6¿xRWѦ!gդQ׭lk~7fsoV <7W5 ;dK%ѧO+K]EQQ1$ҥ<4oOy~Te/gNn »2u)У:qZ5kTt*یhSddB1m69K98Է'V>٩OQ?p'FIkֲ[-k: V u9iZg$R!n %yOg[Ve: 1?qgKvu? $~ wj#,Y gd۴ۈnM(ʒSRSu9c+G5,ݜT:n#N^g9NqitS5JӚ2JJI6ԭtweƝeƧ5m ݤ-峼6ȹk\C>c4YE횗 W>xZuij(w:#[{vmSXRtBŒR5G׼eڃhMImxlZ}=[v>Q^)FMXWw½?}K>o%]GL/ew-ni$]Gܔc8{hӫ,D:rNR:S4KO}jI/a I>nE)TRXSgw_W4kZLJn.b|\tTllč6+]O%wSE]ZOlԿ'drhgX޵~5|ZEXxbRg^ɖuѡy :}ZL/Y> 𿇬W|i&t|B.| g].CLozG7l#M srE}Vrc+ƜrU U#:֕:E*+*<|gRmmJ؊n>JR ٩OQ?5/ ?'nҾc_?DԼWwQ4cN4us_[#ƭ_]V };AMGlb5ோ<+}Z*|1vwE:7 5[VWYOmiz*?Z<5+yfY6*tVj45OHMB sRNiĄیFDuRi9RZoJU'.GYN4{}oL1_Zs<=KzI+}ླ _Ļ+ᅤڇ>'|$55:©Yk~+; !t4H* ƱuxCٺψ jEAÞ iO nu 'GM;B#{v>~|R[JrNQ\{ڵx&y%EM{M%ZQ7i q{ӄ%Z٩OQ?5/ ?'7SUqb<)e>kqe.gqsk3Y]M7.-dBj1>kn` #=?kiEFIIUzM5!N5*FV6KJ&(4qR6j_2O9]zq_5_D?L_٘}{ yA|We{KzI+Cwf.f - ?hKҿX?|&S%8wU37Wxj_2O9FKzI+ gkgfG{4|[IѾ pjV~ xēxOQ>!/5/'?+>&|8H҇ ~S|8uc~Ҿ9~ǚm{/[jz? i/O|//5 RmS|^ ]rhUJu!Vt :t*b*ʝ,L0Rk(Օ|F BJR+Fiyk*_!: ;3G5/s?C%_(ư,?LyWg;󠟳34_R_O?<>8t?ZEkNCWg;>yk*_!: ;3G5/s?C%_(ư,?LyWg;󠟳34_R_O?<>8t?ZEkNCWg;>yk*_!: ;3G5/s?C%_(ư,?LyWg;󠟳34_R_O?<>8t?ZEkNCWg;>yk*_!: ;3G5/s?C%_(ư,?LyWg;󠟳34_R_O?<>8t?ZEkNCWg;>yk*_!: ;3G5/s?C%_(ư,?LyWg;󠟳35ߵJXe ois$r%>]NV[Oso d &5h'eu?<q<ƾ>"6_Si9?mPRinc)5[9"-ߌ`~ih>_]g<[闒emɌdzPbDJKܥ|o2k$IT:wq| L4xMq|A-v"^a7Ƌ-+k%g'o+Fx|^2<&*Ej1| Fm:ua(ItY}#cKφ%Xx.Y~-BxRyƁ]wN<_xNK4񶩧Y[OTnvQ{?­#F=¿4ߊ펛^|Iᕿ5d<]-#еHZ}Xd8ؕ'S_:\:r+Ζ[c__ S^,M88\iB1,Ŏ6I =%.UfWƞmL|-f?.'ʰܗHb*G GkcCt6vQc?~0tJ? Z&{ -vЭa fB~y󸮓JLӴ]K4{ =/Jl`Ntx,llUV8#E 9n2檼 uU:.j7|nֻs6qowS¼>&|!EVXHgY/2UTb<yΊ>`>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ>yWg;3Wg;>yΊ+=I^۟xƺw[KѦlV%ơjzD"]ic{ys{'$߳ſ}~^,5zMڦyX-[V /P𝵾eqz4M kaw$XWTiap1JX\ O ^^0Qv_]Rlބ\gQZL]L6sqb Aϧ(KWHY תkK-Z/|U|MwQ^Ӯ4-6;Em]ZMa Ծ?iώ??gozSC/l5_Goe? I f7\Il"¯,Wk\(vc\Z.m:(մ^ZY_\h8汻lKYF~& xgET|b+G'^9xl ߇U5qm=IXMN;ȭ-k s,%,)p\dqt((Txt}J"uʭ.1XlWa4J#wB!Ru=Rnrc|GMo&Oxq~՟Ck#3Hޗየ ZVz7gw}$,/;9 [ Ef7_*< ԿY cH|Q௉t9ҭ=*+sKyKW 孱@7I^?ie[E.1:t:K ֶ"3UXIN3TS̸K^Uy\&Z|8)WsU`IxTl.MW 62 _xV$^ cKCg-nna}S/Z|St^)<7vXѿ"͝OLMr2CA$6̶&(uR4ϋ=+.c<7>d[/n54~KMJKKf!Nsߊ~%?<1Qo×?.t[|D>:+zuG6gg[7^kU(QR2dE\*9#~jPƔ+|M۫Zï Cmg/hhn+Jݛy)oRo_/COoj~??a?/Ѿ xGOt{ "S]ܚ^vr@ |xᦳ–Ŀ ?wY}$6a}0K6[o"J: UyNʤ(S25ڍ:ΕZUz*o5S2B'ƶN4)Tr)SRn:fFj!Oo|m'>8x l[72]i~mXB$:W}CT48+di$l"P#hp:Zj>/'W& I|9{DZA:Oz$}Glx,S>./'JR0tIb2i x՝u_]?:LJ3=xgJ<_={+>D𝏄5}:OEaA"]:_4?mgp>0> zmi'M'|G݌jxWDi21\x aӛJ~n_қ.N 0aV*Up*ʴMYP,<8eJ5P:\3rJ|CˣKFq9K B""QeU xADx>?­t3j<1\iq3ΑZM ~ifG%|0|Wxv9?i: qj-f4rZX\} /->A ZG?i߈?7xFˏ j5Ogj(<]Ox"xT7z^? i$Mok-M?<-[zf l5[XΨŞljizk-> ѧo| k[ "RyzV ƫX:hr*uF.q'Jc RuK{p:BnSm4VA%}-~7>Ѽ]_L$~%ZHm֖Ie%x%FOW+}xmnjuxgU!> 5]FLյMXW0|>}{Am%{*C4V okۋឿfG%xoW&^^A5_躦ܤ}M&>Ȥ9µ:*FWC :9m yu\ׅ:'lDiSSN(*kU5pЌi{ xEzuhM:!EFhT*.\=Rglُ-i OqRPaׇxu]BWtmkz]o{ews j1Z7 ~_~!o_h? k^7xÚlZ_mtNXMc5m|-sh<;uAǪ7'? Iž Mw׭/;Q_Nl$?8yd}Qr;¾ 9y𧇴^G?f-[{xy5KwkfiU\MXSq 䮕4?, )00ycN;('"ARVx5tj뇱4JhcXT:;D q ~Ѻ7 Eyp?kZi ;a/ڣWAxAOo:goah:?>"jz¶Q[jZ̚շ5lWĐkj֚ZiO4moqo-}ptDw Dc&Yq"YgHE˜̻KW*TiJt'S)swLFENF# U+N/j5t"_ɉn6t5c(T3R>W*Ou*T13QQyLo]IxQd|umpCc(nl|q"O:;EK46}K_,#_~(? k]'-%3?;7zΉ:?Zvw,0]4ϋ=+.c<7>d[/n54~KMJKKf!Nsg[? 3Lxkex].[4k;-JY/颴I ܣFJS6<\l-=\w9h(_8Xң9J*u(UwRyoVuQ*qv""+#|%YT UgJӅ{4A]@ּoữ IґF5o<}]658q麤P9S~ kw4?Mڟ],X4NR{ϩYC<ߝ:/1q1ȱ̯ |Ul% 4g^Wx݇, L',\z3|NեC3Vp&7W?Xc-hVT-hG*N.??िO5,?֞~ ? گ>%W]^Z ծ+ey"աqk2~.D?gʞ+|CϏ+c%~(6y ֧UiCeg<wW\K/[ĺ/_CnwY_كmx5zYjn+nZ4vXZvshkCss:%xT?4k^,12O7*ZAokqy>ke=]jyV'^J|7Psr5r.0Lxx__ a񖃩xWZ\;٠SZ i뷉jgxh?4B[p|J'?u7+O|e[g+XdԤ}=')uwZ߲w<1,?4 #^D6u}>PK GP{\wqs\]XI2([nn&G=hu6C0~# ø]C XE&~ʆ"T(%[? Tbgu_0\ibp B\}QFG۟>~~W_|q{sWNQRXMtfMmF(-VӬͭͭm`O\/ j2tvq4Ky6a!qP?{hiaRT8 Q?_&5SF1'bpJ}h)۳I +#@((((((((((((((((((((((((($I')i)duYGԂ(o?:ֿ/~/zyixV]'\ϯkm z=͕up~[h[KO_Uս ;[o.mܻ9_xo#v6c^P'[moD7tNb<{V,lmsMHWY\g[{/v3ȕ%ٿkmݷn9q{zڟi{{u9'Koϗ}7w-ÿS|i־?~v~X#xwzM#cĚ7iw~4[ &Vk{ie[SCsLji9ݮ䋓q|PGj=YTrM׼%OPԯ4cܫ zg̰d,LpR\S )ԒJnSN0+>ٺ"(F3IsMԩN-F؅MF5UZ6N7k(O?Yk_חo;]#@ҵky&XMwmi^~4&זC4~ڝ4"|a>i^)mbImgw>#]Zjֶ6]i%l~|ԚM [=~?mus\xMOxQS/m%-ĶwI]P&'i~">2Kiv RƏ\ڏk B+_/si)L\ S稢;*1)8*MԨ׳yK^>g_F?WqPG,D25J-s/Qhӌi{*u)Uuh~ uM[?47?Z??{iV,;+Y!mV  e]AdH|;)xh<5?i2J5*.Ȏ Or6cGx Bεo=>[=h63xűŵnI,}Nfo"8@~UK[lS5,t$ D$9<*$Fs||tӌg?d*b{*RJ10(¢HNs$)ATy>OjJtu(ѝ:8b)֔9iF{ῃ$hQ Elu]>Yu=.iQU%KeETKf#-z-|+|6>|m-sj:FuwDmu-V_`^w^.q3L֏ zx??j(CEvtW M4Ż4X WW5kyFKt//&b{9.$XCGz댱<ψEb'0~ڟ?i_xִ>/#?mx #|7MT^8&2ӕjW+'KӖ.$](aqժUІ߅ TQԥ5y}oox ;O?T-<9?<-c>|A` &cd76 < wwQ|mO{a¾ =KĦA=5t}mw~6mO57nX<. Z'??g</5O:_x?~ڟ G4#'9-LJ{o,[ƗZ9.uOrU8N5kC)t()<.gj8hС՜gFV*yv0D=xSrcժܣ)ex/Bիg Zֺ_M / մ&ލK_U+p|(ZZGu Fx:;! дtMRӵ;jZdhJy<)֖I8z,]Q'S*ЧS _ '8 aTjPi{i`<ԞQ1!Qb91ΚJK֢?VO~sOྷ_'0?Cmj~&m'Xïh~9xmgo in_j/b@ոP/)֍~_Q*/6kmi^6RZ5W>цoWmb>5ԡ:}6OK/F-تebq1Frmb~JNTJ(SI ӊυZiFTn$bB_V™īSUR**=+d_f*= ?m/CqXO핌% S7=d|K{i/i8Ǎ~Scᶉ$|~2xo:_|/uwJG___|=x]մ&Pl*B*R, ӛq M,^Ajt)OFx,\YTKt hU^ʭ\ JVgo^pѯ*dpbBؚZt% gl|s} ^L|awsXs>(m,|A3ikk V]H;JE֡)FrV8Ƥc'NrcZ*nV:;N$W6P^1pzTF29J*#5JJdҕUMxTZ(Ꮘ&|J >?OX#7J}'MO߉|mw ?k[ P-躌Zd_ii[}_ SQ/ӥ^jum 5WVt*8_ɭBkSyggǝ3K{ E~$YL.φڟǏxE&>? O`yϊ?ş_k kv>xWź_|J< 4W'IEk=zΡ\M^uԿo -X|6P׍64 @.𕯈4[;RJjƯ/|Xe Z|E+KY5%\gMԍEWQry*\-8^&l=%:ԪB?m \qG۾!Я/5w߆|1c>(x.k|CO \essxK,g{;LҨ wֶNtNHT9S +Jg.2M5ѣ8N5! *F3ҌҔdiw?⏷|C_^k+w?⏷|C_^k+8Ϸ|C_^k(w?:(w?⏷|C_^k+8Ϸ|C_^k(w?:(w?⏷|C_^k+8Ϸ|C_^k(w?:(w?⏷|C_^k+8Ϸ|C_^k(w?:( U s;*;+zqmqkey)!-kveY#hG"3qcWg@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@g9;Wθ s?v?<]XCGz댱<ψׁO)~˗?s6|ojw/xus"xO7Bd-r\K^E ݜ*GݩN$׽٫v@J֜SJRdݮ+KQ>$z߅w]'N>/|%s|Uд}=#E>~9t*G4;v:{1[1K?'q{L߄q/M?]YuI𽦯s4M>oGy4K$3 EO,l8RՕ:=mZj~u=%-Gwu+hU#+s{ДIr{~ &9 hď-Z+ &9I&K{HY31<3DŸv|)6׷-ig\]NnngyY]ۨ9\9O]ШП=J)^%85$*1Pj)SvP:{*iOzJ9'%j~..~κ5?i~:Լm}Kߋh1$YVu<:_gW&kKdٶ?fOٶV>>gqsZ_bōZM~%)2'o$꡹o>iir(ݍ>IЩf9japӏ-g%iRﺎ~{΢+Ғԩq4tሯըV> i|G'+1 ¶|T6r;å#xO=T-&GF? ~~xW]|30[x'EkMͶXm..mlR YTCJ8llb(84E(9Jsܥ)7)JRwrwmmwdF1cEF1J1Rc(+$I$I+ ) 4+qw|Pin [ۙ&{{D< 2$0x C(i8K  FMS"ZWkZwk=C^ &5_ xgS}h:UTI^uͤKCpǽdQkE+!3 }j8K)EIUTNxJ犫UBJ>y8N-mm57/fW[9{哯MJ6qU-S(Ri4]QӒ:4~g |O38i<5ji!O#xB~m롡>%ڷ"^]kvi>6~7M@ߎ>a~%$~_ZկIO6_hK$+!3 }hq?/ MjP*%էVJQg,5l.M2_+ 7WT.T>XSJ+WZ5hW)|;Ë-gƒ'@Zy_r|Xź^" qm91xxvɴZX_zޮmu~> :Ï?>[N:?Uŏ/]>!|,_i/<<^>#"W>q?/ >Ї%?AIj)FN3X93RQO Z)N*a6ۓ)ZNs2SW2N.QbkbiקMƤ);^%4 CY|G6S]kܶVɪWZmڥFnӬ-Y %!M?\|<KG+!3 }j')JNדrvJ*1J1Wz($IE($ۓVRnR}ܛmggEq>Ї%?A4vtW ~ _C?\|<K@¸yB?W?Cg gEq>Ї%?A4\g+!3 }hq?/ vtW ~ _C?\|<K@¸yB?W?Cg gEq>Ї%?A4\g+!3 }hq?/ (Ï^x:tx7HPҼ%2=VWy<76֑M C&]HQ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (8 s?v?<]q^x:+n~=8g|ϴZs:] ;O;a'6ew&ޥީr m3$ӏ*%宎Ox2/>Ƿz(D/սk3hP_ͫGdl 1 WtW ^&^oeWVSs9J3T9rʩͻ(Jxj|vQUbUV{?fRN֗'4W7~x[տN].(oӿWsˊYxO=55OkVs>m̺&mK>%16/jwXMsFu{Zjަp^ mזNמTtx?}>]Ngz8I82(8855/yZ+V?w qKVjZ&XꚾiVXA%jiccemwwSEoorM43 Vv]Z ͭ1\[\ʓ[[̋$34l $H$ח˛'}%kN[ߓC#g_Ei ~5Hdo? i4hV ^X6w^\Stׁ!-KSJ';m#SW|#爵YAjS7KkIo6=u=֓kH?GŸL Ҿ2|Bh&6Tuቾצx C?iO>/u7Þ=. K63⧄{Ş*ʸÉ?6'7kpkJJ5䰸zVacFZRF}~9;MڿiZwd%g?)Fj'ď_xZ᮸|qi^qGO1xS7w5cԮnvh,DeNJ ,ԫӄe*5AJ\ u|?4<:28Φ21cԟ"XN\*Ό8_x^u}X7Ï4? ƐO|/ %|=ij:,֑j]CgELܤMu+ƽ)ja*CZsWJG`!::KN]Nj(4IU?PSgNToc*F:pq|P<_2X|il(uAO.|Jվ\7g9-kŞ#2x*=CH֣|1ς *эm 0QQUSoVi zqp:qѭ5ya0\=ZJpa:2TjU֜:ܕ(2"xbԯC4vk i1jNا:~ kgEO&O|&X|IͲ̓+72%[O *e T*"VJoRKZ+N%~K/n~Ӷ>1߈'߉#Οa&x~_G#cu^U>Ei7ۼ௱-]s<Df>½V-K3Y3vwkiڭܚTUլ],I.|̶h4@G-;w15 FD?(?xy@4gCc֏%wo(\q?yggCc o_Z?kN('_|s&W-G~'ouEohڭ) _]Z) յOg;qZpzmU*k*q:Z Q}_ ܋?P`1a!eXOT(*،T0x4R./[>0>&x_47l~׿4 _~$İ>%ύ5H旧x7w) rTn4oF|b_࿎~!Yox M3yZZׇ;կ|M+{sNֵM/O->xt;[ f<5 o Ե?zrcLڅܻ.&I$nA7I.?|[I7Zm|EEĹt-bjzVg4oZk};ĭÒW[4,S.OѡpxΎcӝ\5*rW~TpUBEԯS^0y^\U*9 :X^Yi<Lj{3] s̮qt%>iVU\+ϟ_>k j_ |.kLnk=jmZA/u]R=+qOEW~,fSb7o_RyMGη=2u'KK?,tvݏU2F?kNȥ^9vUԕt`UAqy69QyAB0ѩ~O^9p7#*T^ ,*v&/8G xsb\No5N3C ivuG'*)K(qtĿk/^S$&?UhOo.ʹ<7>&Wn-;w1~UԟHE,P?Rg _VWD7ڿsx7Squ}oi~$[tJ37g >w~G Z3~ A-L|RCh]GqgSÏ4CGji&;\*Si৙ҍݔbI+|u+m3ÿ1h/J5'3Q FD53m3ÿ1h/J5'3Q FD/?;ƴ(?xyG%wo(;?;Ə[ w b\q?ykN:֞^-U>֞^>oo~>#!x[GL_t}nWUhAO J>%HlMޕߋmO/4R? YC9 53=񔯹/3('~|[~>sYyk4xV,oY|#Ѳpt0e;+YU~*\0\%.O(Kb.“ڧ 5O'e~p7pOqOANďp=eG x'֧6CNXK,6է-Tia8wRS^0ui$$o+3־I6|5I.c+}kRB!)LE rHBGqUh\KWNu?:g}/Vtۛ'IȾ i6_$͏{h?Kz / ۥ>#[X ψw}I7>˙yGƩ]\\*?7t8eԧH6_E?^yX]s|CөPD-,Rd[|ThlW$#?` xz3UH(̀?!a'x/ymJеۿ2ޮo˨j:}kj5<'{ci{phy/~_z>֞vYF_sae)a}cJS*StМh`%|*|C+ |5\ >q¾xQ\/C1x8y^cƘRa0չxs@x[:xG26c⧉tdJѿWkj׼I#$V;%杫gkOQ'>"~߶|DW>?Zx]gS!<- ? x@վ!$ԾCHz_I:!MNI=1 ңZkTB[pP'P"Pʺμ8ܳiq3-gB<1a3 -ȥ|*֛}5i֋O~,|gt>| "i/]Zf/{ ×w?o?Ŀ Weq͋^i[O4-xs ^"8cd; ԭ֞^l-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-U>֞^-Ÿ_7"~ c w$Z'𳿳~jfei~{+q+}=G׮`~}b12GqyjB6S\|9N:s]}WO9G+ſVWx(la1_eX}v/o0.L]=kg/C@ mx/4Ex×ZGtbdYݬkοHO(`|Ѫ z'oI#zN_<ݹ{--rڞ}o^kOQZz_vqϱYcs\+ f'^U0iդWR׵MEBPnjQp|7|Scùl <2uLSVQN&sU_:ssR(誟kOQZz_n^n^n鴽Y7Nҵ` Mig5k*/>5׎luE*|Yx;5A~_ig/o4(xǚl^9xپYϩxG_D]KF-nO쯄'񦥨V:ico5Zf;"<=㟀ZEOxC '_Tkz4w4/Mu·SɩMz\QT(.]P1Qvt3\J~GC,+'XR)8MTt\jj8Wh[U)ωm5nG;ƥ}|_;ZGíc1x67$|%KƺnF _,C-5u{Z['Po^ѾS:4=sX xsv?, NJ5LαwȚ6MNIZ49e y_:14RUe8F`ܕ拧V],<'V[gF8ԩVwCޜޓKS(߇Կw?7&5/k_B?e|Fg}gE׏()ӵ s |>~&|Mh<[CQTx'KKPѼ;k=~8R_i?|9xžgOɬxÿ-$RvWKpi~.|NCލ\>̪׌׸ Ze%NҔԥNu?F{ Zz^W '*Z*V焕G84J;QA1_FoÿLj_?l߀"i <]_|@Դ^ysG <%7~|S/QIG kZƝi^(/kZ2jx_|eN_xsn|U c>|eּO?>:'[?u88{E8rΤ90q潹pRc퇔86I89FkԔaTONNδ!9S[_cG;ƥ}QA1__! :ĿZg/~:MmG\[|DU?UxoMFWl<ݴ-%zটGMC_ <;o{ß!ho8%m+W#ѼA [4o:ψ'5Mcm]RQi9ST4Цe't Z&]O?|e|7'⿍>y᷋y|}>O>^K۽|Gsmhs9FƬڊ)ѣHnjr05xʍz5Sp IܜJOI^eS㺞 sa(}5nG;ƥ}xwV c<r_G9>$6 xQ͢/ghhnnvWYCe|D[3d~ Z'|DN>m?h~#_eGE|Ogy Z\UDN*ө:t9ЕKtcΣzWhPj(ԦN+XJeƣNVQA1_FoÿLj_^ tVk_>5Ax⧌[A5 ?gX_Y|7e[]_FЯ|KQ?$Mm;?~6os-?wO~ Ifuox/C4~&x.c%(ߖ+ ˟nTF,y]*U PQtUI?iNRJjQE׭KS-N"8TI7i(߇Կw?7&5/k? )(_x_'>!Úo/>.[SxGs>&xn~xkP֗z2X/>zWoWߌt|(_~ߌB gcr|5j4T#y⹊PZj2u(SKՍ5>y|E_5,H8S t*rNIs1^X/gNܩxZE5xī{cY=gZZڴW1ߧ$٭NE 8c__3 w )oL${ ׀4> x:ڌ K|?F~?.Lw7B|+=qſ_ǽ\X~_\?g7:i n642i(AԧڙUiʥQZ ֛>39~ W ::5*QI\.I:*m/('c ߠ 7cύgם5>)ڎHM:=5.5iA 1_x jBRK}^&ZHa{gmXO;:[H I'Y9N(YSUe~6%*ԣJ(*si4τiNaJJj}ҋ8sTjUi)r)R:u 7c_|o?ᴿࠟY~NqxO:l=MpZ,z~-Ťie}ww-ln%]]z"֝pfOһj`H+u+`g<ۮ|@ի$y CxY?O?:Ovo=oY?uKtvg;<5].|M.t_5[CQ5\hrou}V''2I_Mq%~>( ~jVz_O¯:cqH26@ whfEYyoGeh$_՗߻U@/Kh_>x'.$~7YTr đ*HV 䘙cx1RHa8ӇGITI{`dY??oݿdY??oݿd&xOo~մ{Ή3ǺºX/؝0q<7ymN-al h~%C~}Qg:TrTj{ZRkާSt:)ɦԜ%xIaq_G#6=J?C߂+9RC tierpx*ҋ rRSf*QKO[^8|^*m={Z9Ir1yիFu!nJZ}VqxYbia2*L-J>~3Ʒ},<'{9dè[mWώx<3hlmVM$~+Aj?~ 7c?~7d >jO|C7.o߅v6:˽RĿk=/ RG#6=JY̢0ucj0xeoc'OZ9RQJz\Vu)b`&ӫQk,6";qi5"Ztt|; χ_MTXӖè&_67iiD~3_VDžS hufwO_?<%7~*~x[ zzJ|_sgY4m+SÞ Fj7Z$CMG#6=J?C߂+:Z(IJJU(9|МeycRQsn&9FYTw[UJU[<]@| ߇k | _Yj0ֵ\>1|*o&y T&;×NAk[Eu-o፦M׿g5~|%D|Aif7^9gCmEM-vQ?+hO]G#{_}wg*TO6v߸C߂(?~ oԮkSz f9kF 8F_^ͳ ҊuFq zT#R^࢛pܡ)Ūy.Y.YI,,dJΧ8|QMƟ4O |Jk ~ſ w__6!k_i߄`׼Oci>0扮jMZ%׾uQ}ᧉ5ojC[ԿhJCOUVwux%|1 Emj_5(ukX4?G#6=J?C߂+4`⚖4JPɽ*ӄ=,hIWuRuΦ.YsFB~Ru"WK1E'JHʛCy?ٷc~>-Ҵ)RᧃEPOèo hmh uմ  ЮFws_u Iu|?O?fSDC?_V/*>1XjU444CWoWQ:?Gmz?VRxn㊩_u' N]ףʰ\M:9vJc %xzPR%(J*,6&U+zUQ)ӬAÝr?~ |ik7ĿWl-xx;_jD?m'WR-/_Zssi_ ~y~[BhΕ~ h~'lj,|iOV ΁gEkmo6#jߥGmz?T(FQ001RPXl.N <9RrJx*uu *ڽjPO?oJN|Uc͵r4a9^BPtxRW? ~i+-wC_]ö'W/_bMe|AW6t ;-·׵/ 7W770'30~,3lD?Lx_L<#_WZ<>es+ o_ml}·Ϸ)[_ X,&uKU+RLn+:yUUJ5q5)Su:U& &ϋ6/`quaR*xZq#Ox9R䠥8Eɥ)E7vO/(5c1;-i? LJ/u.Mմѵ(yeq< Q\Hjߵ(<{ᤞRh^^,k/?*G4kDfٵ{-,% 61ٟoKog4t_g.8k eb~[?Ua<~K+ǚO xğ|Q[M%u_G<5;{sR,JmfΒ8fxfN*kХX UgJںR|4b? ˳ 7cx+XxG΋aKisͪϾR'^|Dˏ%\K_^>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ>v%#??z?ξ8? VY]jk3\æ[+K[uQni -QEdjango-solo-2.4.0/examples/000077500000000000000000000000001470472713100155555ustar00rootroot00000000000000django-solo-2.4.0/examples/config/000077500000000000000000000000001470472713100170225ustar00rootroot00000000000000django-solo-2.4.0/examples/config/__init__.py000066400000000000000000000000001470472713100211210ustar00rootroot00000000000000django-solo-2.4.0/examples/config/admin.py000066400000000000000000000002661470472713100204700ustar00rootroot00000000000000from django.contrib import admin from config.models import SiteConfiguration from solo.admin import SingletonModelAdmin admin.site.register(SiteConfiguration, SingletonModelAdmin) django-solo-2.4.0/examples/config/models.py000066400000000000000000000006341470472713100206620ustar00rootroot00000000000000from django.db import models from solo.models import SingletonModel class SiteConfiguration(SingletonModel): site_name = models.CharField(max_length=255, default="Site Name") maintenance_mode = models.BooleanField(default=False) def __str__(self): return "Site Configuration" class Meta: verbose_name = "Site Configuration" verbose_name_plural = "Site Configuration" django-solo-2.4.0/manage.py000066400000000000000000000013511470472713100155410ustar00rootroot00000000000000#!/usr/bin/env python """Django's command-line utility for administrative tasks. Note: For django-solo, this file is used simply to launch the test suite. """ import os import sys def main(): """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == "__main__": main() django-solo-2.4.0/pyproject.toml000066400000000000000000000033331470472713100166550ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [project] name = "django-solo" description = "Django Solo helps working with singletons" authors = [{name = "lazybird"}] maintainers = [ {name = "John Hagen", email = "johnthagen@gmail.com"} ] readme = "README.md" requires-python = ">=3.9" classifiers = [ "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Framework :: Django :: 5.1", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] dependencies = [ "django>=4.2", "typing-extensions>=4.0.1; python_version < '3.11'", ] license = {text = "Creative Commons Attribution 3.0 Unported"} dynamic = ["version"] [tool.setuptools.dynamic] version = {attr = "solo.__version__"} [project.urls] Homepage = "https://github.com/lazybird/django-solo/" Source = "https://github.com/lazybird/django-solo/" Changelog = "https://github.com/lazybird/django-solo/blob/master/CHANGES" [tool.mypy] ignore_missing_imports = true strict = true exclude = "solo/tests" [tool.ruff] line-length = 100 target-version = "py39" [tool.ruff.lint] select = [ "F", # pyflakes "E", # pycodestyle "I", # isort "N", # pep8-naming "UP", # pyupgrade "RUF", # ruff "B", # flake8-bugbear "C4", # flake8-comprehensions "PTH", # flake8-use-pathlib "SIM", # flake8-simplify "TID", # flake8-tidy-imports ] ignore = [ "B904", ] django-solo-2.4.0/solo/000077500000000000000000000000001470472713100147135ustar00rootroot00000000000000django-solo-2.4.0/solo/__init__.py000066400000000000000000000002211470472713100170170ustar00rootroot00000000000000""" django-solo helps working with singletons: things like global settings that you want to edit from the admin site. """ __version__ = "2.4.0" django-solo-2.4.0/solo/admin.py000066400000000000000000000072221470472713100163600ustar00rootroot00000000000000from __future__ import annotations from typing import Any from django.contrib import admin from django.db.models import Model from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.urls import URLPattern, re_path from django.utils.encoding import force_str from django.utils.translation import gettext as _ from solo import settings as solo_settings from solo.models import DEFAULT_SINGLETON_INSTANCE_ID class SingletonModelAdmin(admin.ModelAdmin): # type: ignore[type-arg] object_history_template = "admin/solo/object_history.html" change_form_template = "admin/solo/change_form.html" def has_add_permission(self, request: HttpRequest) -> bool: return False def has_delete_permission(self, request: HttpRequest, obj: Model | None = None) -> bool: return False def get_urls(self) -> list[URLPattern]: urls = super().get_urls() if not solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE: return urls # _meta.model_name only exists on Django>=1.6 - # on earlier versions, use module_name.lower() try: model_name = self.model._meta.model_name except AttributeError: model_name = self.model._meta.module_name.lower() self.model._meta.verbose_name_plural = self.model._meta.verbose_name url_name_prefix = f"{self.model._meta.app_label}_{model_name}" custom_urls = [ re_path( r"^history/$", self.admin_site.admin_view(self.history_view), {"object_id": str(self.singleton_instance_id)}, name=f"{url_name_prefix}_history", ), re_path( r"^$", self.admin_site.admin_view(self.change_view), {"object_id": str(self.singleton_instance_id)}, name=f"{url_name_prefix}_change", ), ] # By inserting the custom URLs first, we overwrite the standard URLs. return custom_urls + urls def response_change(self, request: HttpRequest, obj: Model) -> HttpResponseRedirect: msg = _("{obj} was changed successfully.").format(obj=force_str(obj)) if "_continue" in request.POST: self.message_user(request, msg + " " + _("You may edit it again below.")) return HttpResponseRedirect(request.path) else: self.message_user(request, msg) return HttpResponseRedirect("../../") def change_view( self, request: HttpRequest, object_id: str, form_url: str = "", extra_context: dict[str, Any] | None = None, ) -> HttpResponse: if object_id == str(self.singleton_instance_id): self.model.objects.get_or_create(pk=self.singleton_instance_id) if not extra_context: extra_context = {} extra_context["skip_object_list_page"] = solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE return super().change_view( request, object_id, form_url=form_url, extra_context=extra_context, ) def history_view( self, request: HttpRequest, object_id: str, extra_context: dict[str, Any] | None = None ) -> HttpResponse: if not extra_context: extra_context = {} extra_context["skip_object_list_page"] = solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE return super().history_view( request, object_id, extra_context=extra_context, ) @property def singleton_instance_id(self) -> int: return getattr(self.model, "singleton_instance_id", DEFAULT_SINGLETON_INSTANCE_ID) django-solo-2.4.0/solo/apps.py000066400000000000000000000001601470472713100162250ustar00rootroot00000000000000from django.apps import AppConfig class SoloAppConfig(AppConfig): name = "solo" verbose_name = "solo" django-solo-2.4.0/solo/locale/000077500000000000000000000000001470472713100161525ustar00rootroot00000000000000django-solo-2.4.0/solo/locale/de/000077500000000000000000000000001470472713100165425ustar00rootroot00000000000000django-solo-2.4.0/solo/locale/de/LC_MESSAGES/000077500000000000000000000000001470472713100203275ustar00rootroot00000000000000django-solo-2.4.0/solo/locale/de/LC_MESSAGES/django.mo000066400000000000000000000020761470472713100221330ustar00rootroot00000000000000\!M Q lyp$H,u}!%(obj)s was changed successfully.Could not get the model name '%(model)s' from the application named '%(app)s'HistoryHomeTemplatetag requires the model dotted path: 'app_label.ModelName'. Received '%s'.View on siteYou may edit it again below.Project-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2020-04-13 16:13+0200 PO-Revision-Date: 2008-11-30 12:12 Last-Translator: Language-Team: de Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Translated-Using: django-rosetta 0.9.3 Plural-Forms: nplurals=2; plural=(n != 1); %(obj)s wurde erfolgreich geändert.Model '%(model)s' konnte nicht in Applikation '%(app)s' gefunden werden.VerlaufHomeTemplatetag kann '%s' nicht verarbeiten. Model wird in der 'dotted path'-Schreibweise benötigt (Beispiel: 'app_label.ModelName').Auf der Seite anzeigenSie können dies erneut anpassen.django-solo-2.4.0/solo/locale/de/LC_MESSAGES/django.po000066400000000000000000000032771470472713100221420ustar00rootroot00000000000000# German translation for django-solo. # Copyright (C) 2022 # This file is distributed under the same license as the django-solo package. # Conrad , 2022. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-04-13 16:13+0200\n" "PO-Revision-Date: 2008-11-30 12:12\n" "Last-Translator: \n" "Language-Team: de\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Translated-Using: django-rosetta 0.9.3\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: solo/admin.py:53 #, python-format msgid "%(obj)s was changed successfully." msgstr "%(obj)s wurde erfolgreich geändert." #: solo/admin.py:55 msgid "You may edit it again below." msgstr "Sie können dies erneut anpassen." #: solo/templates/admin/solo/change_form.html:7 #: solo/templates/admin/solo/object_history.html:6 msgid "Home" msgstr "Home" #: solo/templates/admin/solo/change_form.html:14 #: solo/templates/admin/solo/object_history.html:9 msgid "History" msgstr "Verlauf" #: solo/templates/admin/solo/change_form.html:15 msgid "View on site" msgstr "Auf der Seite anzeigen" #: solo/templatetags/solo_tags.py:22 #, python-format msgid "" "Templatetag requires the model dotted path: 'app_label.ModelName'. Received " "'%s'." msgstr "Templatetag kann '%s' nicht verarbeiten. Model wird in der 'dotted path'-Schreibweise benötigt (Beispiel: 'app_label.ModelName')." #: solo/templatetags/solo_tags.py:28 #, python-format msgid "" "Could not get the model name '%(model)s' from the application named '%(app)s'" msgstr "" "Model '%(model)s' konnte nicht in Applikation '%(app)s' gefunden werden." django-solo-2.4.0/solo/locale/es/000077500000000000000000000000001470472713100165615ustar00rootroot00000000000000django-solo-2.4.0/solo/locale/es/LC_MESSAGES/000077500000000000000000000000001470472713100203465ustar00rootroot00000000000000django-solo-2.4.0/solo/locale/es/LC_MESSAGES/django.mo000066400000000000000000000020301470472713100221400ustar00rootroot00000000000000\ !M9AQF F" W, @1%(obj)s was changed successfully.Could not get the model name '%(model)s' from the application named '%(app)s'HistoryHomeTemplatetag requires the model dotted path: 'app_label.ModelName'. Received '%s'.View on siteYou may edit it again below.Project-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2008-11-30 12:12 Last-Translator: Language-Team: es Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Translated-Using: django-rosetta 0.9.3 Plural-Forms: nplurals=2; plural=(n != 1); %(obj)s fue editado correctamente.No se pudo obtener el nombre del modelo '%(model)s' de la aplicación llamada '%(app)s'HistorialInicioTemplatetag requiere el path del modelo punteado. Recibido '%s'.Ver en el sitioPuede volverlo a editar otra vez a continuación.django-solo-2.4.0/solo/locale/es/LC_MESSAGES/django.po000066400000000000000000000033031470472713100221470ustar00rootroot00000000000000# Spanish translation for django-solo. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the django-solo package. # Álvaro Mondéjar , 2020. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-04-13 16:13+0200\n" "PO-Revision-Date: 2008-11-30 12:12\n" "Last-Translator: \n" "Language-Team: es\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Translated-Using: django-rosetta 0.9.3\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: solo/admin.py:53 #, python-format msgid "%(obj)s was changed successfully." msgstr "%(obj)s fue editado correctamente." #: solo/admin.py:55 msgid "You may edit it again below." msgstr "Puede volverlo a editar otra vez a continuación." #: solo/templates/admin/solo/change_form.html:7 #: solo/templates/admin/solo/object_history.html:6 msgid "Home" msgstr "Inicio" #: solo/templates/admin/solo/change_form.html:14 #: solo/templates/admin/solo/object_history.html:9 msgid "History" msgstr "Historial" #: solo/templates/admin/solo/change_form.html:15 msgid "View on site" msgstr "Ver en el sitio" #: solo/templatetags/solo_tags.py:22 #, python-format msgid "" "Templatetag requires the model dotted path: 'app_label.ModelName'. Received " "'%s'." msgstr "Templatetag requiere el path del modelo punteado. Recibido '%s'." #: solo/templatetags/solo_tags.py:28 #, python-format msgid "" "Could not get the model name '%(model)s' from the application named '%(app)s'" msgstr "" "No se pudo obtener el nombre del modelo '%(model)s' de la aplicación llamada " "'%(app)s'" django-solo-2.4.0/solo/models.py000066400000000000000000000051361470472713100165550ustar00rootroot00000000000000from __future__ import annotations import sys import warnings from typing import Any from django.conf import settings from django.core.cache import BaseCache, caches from django.db import models from solo import settings as solo_settings if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self DEFAULT_SINGLETON_INSTANCE_ID = 1 def get_cache(cache_name: str) -> BaseCache: warnings.warn( "'get_cache' is deprecated and will be removed in django-solo 2.4.0. " "Instead, use 'caches' from 'django.core.cache'.", DeprecationWarning, stacklevel=2, ) return caches[cache_name] class SingletonModel(models.Model): singleton_instance_id = DEFAULT_SINGLETON_INSTANCE_ID class Meta: abstract = True def save(self, *args: Any, **kwargs: Any) -> None: self.pk = self.singleton_instance_id super().save(*args, **kwargs) self.set_to_cache() def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]: self.clear_cache() return super().delete(*args, **kwargs) @classmethod def clear_cache(cls) -> None: cache_name = getattr(settings, "SOLO_CACHE", solo_settings.SOLO_CACHE) if cache_name: cache = caches[cache_name] cache_key = cls.get_cache_key() cache.delete(cache_key) def set_to_cache(self) -> None: cache_name = getattr(settings, "SOLO_CACHE", solo_settings.SOLO_CACHE) if not cache_name: return None cache = caches[cache_name] cache_key = self.get_cache_key() timeout = getattr(settings, "SOLO_CACHE_TIMEOUT", solo_settings.SOLO_CACHE_TIMEOUT) cache.set(cache_key, self, timeout) @classmethod def get_cache_key(cls) -> str: prefix = getattr(settings, "SOLO_CACHE_PREFIX", solo_settings.SOLO_CACHE_PREFIX) # Include the model's module in the cache key so similarly named models from different # apps do not have the same cache key. return f"{prefix}:{cls.__module__.lower()}:{cls.__name__.lower()}" @classmethod def get_solo(cls) -> Self: cache_name = getattr(settings, "SOLO_CACHE", solo_settings.SOLO_CACHE) if not cache_name: obj, _ = cls.objects.get_or_create(pk=cls.singleton_instance_id) return obj cache = caches[cache_name] cache_key = cls.get_cache_key() obj = cache.get(cache_key) if not obj: obj, _ = cls.objects.get_or_create(pk=cls.singleton_instance_id) obj.set_to_cache() return obj django-solo-2.4.0/solo/py.typed000066400000000000000000000000001470472713100164000ustar00rootroot00000000000000django-solo-2.4.0/solo/settings.py000066400000000000000000000010361470472713100171250ustar00rootroot00000000000000from __future__ import annotations from django.conf import settings # template parameters GET_SOLO_TEMPLATE_TAG_NAME: str = getattr(settings, "GET_SOLO_TEMPLATE_TAG_NAME", "get_solo") SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE: bool = getattr(settings, "SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE", True) # The cache that should be used, e.g. 'default'. Refers to Django CACHES setting. # Set to None to disable caching. SOLO_CACHE: str | None = None SOLO_CACHE_TIMEOUT = 60 * 5 SOLO_CACHE_PREFIX = "solo" DEFAULT_AUTO_FIELD = "django.db.models.AutoField" django-solo-2.4.0/solo/templates/000077500000000000000000000000001470472713100167115ustar00rootroot00000000000000django-solo-2.4.0/solo/templates/admin/000077500000000000000000000000001470472713100200015ustar00rootroot00000000000000django-solo-2.4.0/solo/templates/admin/solo/000077500000000000000000000000001470472713100207555ustar00rootroot00000000000000django-solo-2.4.0/solo/templates/admin/solo/change_form.html000066400000000000000000000013261470472713100241150ustar00rootroot00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% load admin_urls %} {% block breadcrumbs %} {% if skip_object_list_page %}

{% else %} {{ block.super }} {% endif %} {% endblock %} {% block object-tools-items %}
  • {% trans "History" %}
  • {% if has_absolute_url %}
  • {% trans "View on site" %}
  • {% endif %} {% endblock %} django-solo-2.4.0/solo/templates/admin/solo/object_history.html000066400000000000000000000006571470472713100247020ustar00rootroot00000000000000{% extends "admin/object_history.html" %} {% load i18n %} {% block breadcrumbs %} {% if skip_object_list_page %} {% else %} {{ block.super }} {% endif %} {% endblock %} django-solo-2.4.0/solo/templatetags/000077500000000000000000000000001470472713100174055ustar00rootroot00000000000000django-solo-2.4.0/solo/templatetags/__init__.py000066400000000000000000000000001470472713100215040ustar00rootroot00000000000000django-solo-2.4.0/solo/templatetags/solo_tags.py000066400000000000000000000020141470472713100217460ustar00rootroot00000000000000from django import template from django.apps import apps from django.utils.translation import gettext as _ from solo import settings as solo_settings from solo.models import SingletonModel register = template.Library() @register.simple_tag(name=solo_settings.GET_SOLO_TEMPLATE_TAG_NAME) def get_solo(model_path: str) -> SingletonModel: try: app_label, model_name = model_path.rsplit(".", 1) except ValueError: raise template.TemplateSyntaxError( _( "Templatetag requires the model dotted path: 'app_label.ModelName'. " "Received '{model_path}'." ).format(model_path=model_path) ) try: model_class: type[SingletonModel] = apps.get_model(app_label, model_name) except LookupError: raise template.TemplateSyntaxError( _("Could not get the model name '{model}' from the application named '{app}'").format( model=model_name, app=app_label ) ) return model_class.get_solo() django-solo-2.4.0/solo/tests/000077500000000000000000000000001470472713100160555ustar00rootroot00000000000000django-solo-2.4.0/solo/tests/__init__.py000066400000000000000000000000001470472713100201540ustar00rootroot00000000000000django-solo-2.4.0/solo/tests/models.py000066400000000000000000000013721470472713100177150ustar00rootroot00000000000000from django.core.files.uploadedfile import SimpleUploadedFile from django.db import models from solo.models import SingletonModel class SiteConfiguration(SingletonModel): site_name = models.CharField(max_length=255, default="Default Config") file = models.FileField(upload_to="files", default=SimpleUploadedFile("default-file.pdf", None)) def __str__(self): return "Site Configuration" class Meta: verbose_name = "Site Configuration" class SiteConfigurationWithExplicitlyGivenId(SingletonModel): singleton_instance_id = 24 site_name = models.CharField(max_length=255, default="Default Config") def __str__(self): return "Site Configuration" class Meta: verbose_name = "Site Configuration" django-solo-2.4.0/solo/tests/settings.py000066400000000000000000000016341470472713100202730ustar00rootroot00000000000000MIDDLEWARE_CLASSES = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ) DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "solo-tests.db", } } INSTALLED_APPS = ( "solo", "solo.tests", "solo.tests.testapp2", ) SECRET_KEY = "any-key" CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "127.0.0.1:11211", }, } SOLO_CACHE = "default" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, }, ] DEFAULT_AUTO_FIELD = "django.db.models.AutoField" django-solo-2.4.0/solo/tests/testapp2/000077500000000000000000000000001470472713100176175ustar00rootroot00000000000000django-solo-2.4.0/solo/tests/testapp2/__init__.py000066400000000000000000000000001470472713100217160ustar00rootroot00000000000000django-solo-2.4.0/solo/tests/testapp2/models.py000066400000000000000000000002201470472713100214460ustar00rootroot00000000000000from solo.models import SingletonModel class SiteConfiguration(SingletonModel): class Meta: verbose_name = "Site Configuration 2" django-solo-2.4.0/solo/tests/tests.py000066400000000000000000000132771470472713100176030ustar00rootroot00000000000000from django.core.cache import caches from django.core.files.uploadedfile import SimpleUploadedFile from django.template import Context, Template, TemplateSyntaxError from django.test import TestCase from django.test.utils import override_settings from solo.tests.models import SiteConfiguration, SiteConfigurationWithExplicitlyGivenId from solo.tests.testapp2.models import SiteConfiguration as SiteConfiguration2 class SingletonTest(TestCase): def setUp(self): self.template = Template( "{% load solo_tags %}" '{% get_solo "tests.SiteConfiguration" as site_config %}' "{{ site_config.site_name }}" "{{ site_config.file.url }}" ) self.template_invalid_app = Template( "{% load solo_tags %}" '{% get_solo "invalid_app.SiteConfiguration" as site_config %}' "{{ site_config.site_name }}" "{{ site_config.file.url }}" ) self.template_invalid_model = Template( "{% load solo_tags %}" '{% get_solo "tests.InvalidModel" as site_config %}' "{{ site_config.site_name }}" "{{ site_config.file.url }}" ) self.cache = caches["default"] self.cache_key = SiteConfiguration.get_cache_key() self.cache.clear() SiteConfiguration.objects.all().delete() def test_template_tag_renders_default_site_config(self): SiteConfiguration.objects.all().delete() # At this point, there is no configuration object and we expect a # one to be created automatically with the default name value as # defined in models. output = self.template.render(Context()) self.assertIn("Default Config", output) def test_template_tag_renders_site_config(self): SiteConfiguration.objects.create(site_name="Test Config") output = self.template.render(Context()) self.assertIn("Test Config", output) @override_settings(SOLO_CACHE="default") def test_template_tag_uses_cache_if_enabled(self): SiteConfiguration.objects.create(site_name="Config In Database") fake_configuration = {"site_name": "Config In Cache"} self.cache.set(self.cache_key, fake_configuration, 10) output = self.template.render(Context()) self.assertNotIn("Config In Database", output) self.assertNotIn("Default Config", output) self.assertIn("Config In Cache", output) @override_settings(SOLO_CACHE=None) def test_template_tag_uses_database_if_cache_disabled(self): SiteConfiguration.objects.create(site_name="Config In Database") fake_configuration = {"site_name": "Config In Cache"} self.cache.set(self.cache_key, fake_configuration, 10) output = self.template.render(Context()) self.assertNotIn("Config In Cache", output) self.assertNotIn("Default Config", output) self.assertIn("Config In Database", output) @override_settings(SOLO_CACHE="default") def test_delete_if_cache_enabled(self): self.assertEqual(SiteConfiguration.objects.count(), 0) self.assertIsNone(self.cache.get(self.cache_key)) one_cfg = SiteConfiguration.get_solo() one_cfg.site_name = "TEST SITE PLEASE IGNORE" one_cfg.save() self.assertEqual(SiteConfiguration.objects.count(), 1) self.assertIsNotNone(self.cache.get(self.cache_key)) one_cfg.delete() self.assertEqual(SiteConfiguration.objects.count(), 0) self.assertIsNone(self.cache.get(self.cache_key)) self.assertEqual(SiteConfiguration.get_solo().site_name, "Default Config") @override_settings(SOLO_CACHE=None) def test_delete_if_cache_disabled(self): # As above, but without the cache checks self.assertEqual(SiteConfiguration.objects.count(), 0) one_cfg = SiteConfiguration.get_solo() one_cfg.site_name = "TEST (uncached) SITE PLEASE IGNORE" one_cfg.save() self.assertEqual(SiteConfiguration.objects.count(), 1) one_cfg.delete() self.assertEqual(SiteConfiguration.objects.count(), 0) self.assertEqual(SiteConfiguration.get_solo().site_name, "Default Config") @override_settings(SOLO_CACHE="default") def test_file_upload_if_cache_enabled(self): cfg = SiteConfiguration.objects.create( site_name="Test Config", file=SimpleUploadedFile("file.pdf", None) ) output = self.template.render(Context()) self.assertIn(cfg.file.url, output) @override_settings(SOLO_CACHE_PREFIX="other") def test_cache_prefix_overriding(self): key = SiteConfiguration.get_cache_key() prefix = key.partition(":")[0] self.assertEqual(prefix, "other") def test_template_tag_invalid_app_name(self): with self.assertRaises(TemplateSyntaxError): self.template_invalid_app.render(Context()) def test_template_invalid_model_name(self): with self.assertRaises(TemplateSyntaxError): self.template_invalid_model.render(Context()) class SingletonWithExplicitIdTest(TestCase): def setUp(self): SiteConfigurationWithExplicitlyGivenId.objects.all().delete() def test_when_singleton_instance_id_is_given_created_item_will_have_given_instance_id(self): item = SiteConfigurationWithExplicitlyGivenId.get_solo() self.assertEqual(item.pk, SiteConfigurationWithExplicitlyGivenId.singleton_instance_id) class SingletonsWithAmbiguousNameTest(TestCase): def test_cache_key_is_not_ambiguous(self): assert SiteConfiguration.get_cache_key() != SiteConfiguration2.get_cache_key() def test_get_solo_returns_the_correct_singleton(self): assert SiteConfiguration.get_solo() != SiteConfiguration2.get_solo() django-solo-2.4.0/tox.ini000066400000000000000000000017741470472713100152630ustar00rootroot00000000000000# Configure which test environments are run for each Github Actions Python version. [gh-actions] python = 3.9: py39-django{42} 3.10: py310-django{42,50}, type-check, lint 3.11: py311-django{42,50,51} 3.12: py312-django{42,50,51} 3.13: py313-django{42,50,51} [tox] envlist = type-check lint py{38,39,310}-django{42,50} py{311,312,313}-django{42,50,51} [testenv] deps = django42: Django>=4.2,<4.3 django50: Django>=5.0,<5.1 django51: Django>=5.1,<5.2 commands = {envpython} {toxinidir}/manage.py test solo --settings=solo.tests.settings [testenv:build] skip_install = true deps = build commands = {envpython} -m build [testenv:upload] skip_install = true deps = twine commands = {envpython} -m twine upload {toxinidir}/dist/* [testenv:type-check] skip_install = true deps = mypy==1.12.0 django-stubs==5.1.0 commands = mypy solo [testenv:lint] skip_install = true deps = ruff==0.7.0 commands = ruff format --check ruff check