Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

# -*- coding: UTF-8 -*- 

# Copyright 2008-2015 Luc Saffre 

# License: BSD (see file COPYING for details) 

 

"""Model mixins for `lino.modlib.countries`. 

 

These include :class:`CountryCity`, :class:`CountryRegionCity` and 

:class:`AddressLocation`. 

 

""" 

from __future__ import unicode_literals 

from builtins import str 

from builtins import object 

 

import logging 

logger = logging.getLogger(__name__) 

 

from django.db import models 

from django.conf import settings 

from django.utils.translation import ugettext_lazy as _ 

from django.core.exceptions import ValidationError 

 

from lino.api import dd, rt 

 

from .choicelists import CountryDrivers, PlaceTypes 

from .utils import get_address_formatter 

 

 

class CountryCity(dd.Model): 

 

    """Model mixin that adds two fields `country` and `city` and defines 

    a context-sensitive chooser for `city`, a `create_city_choice` 

    method, ... 

 

    .. attribute:: country 

    .. attribute:: zip_code 

     

    .. attribute:: city 

     

        A pointer to :class:`Place`. 

 

 

    """ 

    class Meta(object): 

        abstract = True 

 

    country = dd.ForeignKey( 

        "countries.Country", blank=True, null=True) 

    city = dd.ForeignKey( 

        'countries.Place', 

        verbose_name=_('City'), 

        blank=True, null=True) 

    zip_code = models.CharField(_("Zip code"), max_length=10, blank=True) 

 

    active_fields = 'city zip_code' 

    # active fields cannot be used in insert_layout 

 

    @dd.chooser() 

    def city_choices(cls, country): 

        return rt.modules.countries.Place.get_cities(country) 

 

    @dd.chooser() 

    def country_choices(cls): 

        return rt.modules.countries.Country.get_actual_countries() 

 

    def create_city_choice(self, text): 

        """ 

        Called when an unknown city name was given. 

        Try to auto-create it. 

        """ 

        if self.country is not None: 

            return rt.modules.countries.Place.lookup_or_create( 

                'name', text, country=self.country) 

 

        raise ValidationError( 

            "Cannot auto-create city %r if country is empty", text) 

 

    def country_changed(self, ar): 

        """ 

        If user changes the `country`, then the `city` gets lost. 

        """ 

        if self.city is not None and self.country != self.city.country: 

            self.city = None 

 

    def zip_code_changed(self, ar): 

        if self.country and self.zip_code: 

            qs = rt.modules.countries.Place.objects.filter( 

                country=self.country, zip_code=self.zip_code) 

            if qs.count() > 0: 

                self.city = qs[0] 

 

    def full_clean(self, *args, **kw): 

        """Fills my :attr:`zip_code` from my :attr:`city` if my `zip_code` is 

        not empty and differs from that of the city. 

 

        """ 

        city = self.city 

        if city is None: 

            self.zip_code_changed(None) 

        else: 

            if city.country is not None and self.country != city.country: 

                self.country = city.country 

            if city.zip_code: 

                self.zip_code = city.zip_code 

        super(CountryCity, self).full_clean(*args, **kw) 

 

 

class CountryRegionCity(CountryCity): 

    """ 

    Adds a `region` field to a :class:`CountryCity`. 

 

    """ 

    region = models.ForeignKey( 

        'countries.Place', 

        blank=True, null=True, 

        verbose_name=dd.plugins.countries.region_label, 

        related_name="%(app_label)s_%(class)s_set_by_region") 

        #~ related_name='regions') 

 

    class Meta(object): 

        abstract = True 

 

    @dd.chooser() 

    def region_choices(cls, country): 

        Place = rt.modules.countries.Place 

        if country is not None: 

            cd = getattr(CountryDrivers, country.isocode, None) 

            if cd: 

                flt = models.Q(type__in=cd.region_types) 

            else: 

                flt = models.Q(type__lt=PlaceTypes.get_by_value('50')) 

            #~ flt = flt | models.Q(type=PlaceTypes.blank_item) 

            flt = flt & models.Q(country=country) 

            return Place.objects.filter(flt).order_by('name') 

        else: 

            flt = models.Q(type__lt=PlaceTypes.get_by_value('50')) 

            return Place.objects.filter(flt).order_by('name') 

 

    def create_city_choice(self, text): 

        # if a Place is created by super, then we add our region 

        obj = super(CountryRegionCity, self).create_city_choice(text) 

        obj.region = self.region 

        return obj 

 

    @dd.chooser() 

    def city_choices(cls, country, region): 

        qs = super(CountryRegionCity, cls).city_choices(country) 

 

        if region is not None: 

            parent_list = [p.pk for p in region.get_parents()] + [None] 

            #~ print 20120822, region,region.get_parents(), parent_list 

            qs = qs.filter(parent__id__in=parent_list) 

            #~ print flt 

 

        return qs 

 

            #~ return country.place_set.filter(flt).order_by('name') 

        #~ return cls.city.field.rel.model.objects.order_by('name') 

 

 

class AddressLocation(CountryRegionCity): 

    """A mixin for models which contain a postal address location. 

 

    .. attribute:: addr1 

    .. attribute:: street_prefix 

    .. attribute:: street 

    .. attribute:: street_no 

    .. attribute:: street_box 

    .. attribute:: addr2 

     

    .. attribute:: addess_column 

 

        Virtual field which returns the location as a comma-separated 

        one-line string. 

 

    """ 

    class Meta(object): 

        abstract = True 

 

    addr1 = models.CharField( 

        _("Address line before street"), 

        max_length=200, blank=True, 

        help_text=_("Address line before street")) 

 

    street_prefix = models.CharField( 

        _("Street prefix"), max_length=200, blank=True, 

        help_text=_("Text to print before name of street, " 

                    "but to ignore for sorting.")) 

 

    street = models.CharField( 

        _("Street"), max_length=200, blank=True, 

        help_text=_("Name of street, without house number.")) 

 

    street_no = models.CharField( 

        _("No."), max_length=10, blank=True, 

        help_text=_("House number.")) 

 

    street_box = models.CharField( 

        _("Box"), max_length=10, blank=True, 

        help_text=_("Text to print after street nuber on the same line.")) 

 

    addr2 = models.CharField( 

        _("Address line after street"), 

        max_length=200, blank=True, 

        help_text=_("Address line to print below street line.")) 

 

    def on_create(self, ar): 

        sc = settings.SITE.site_config.site_company 

        if sc and sc.country: 

            self.country = sc.country 

        super(AddressLocation, self).on_create(ar) 

 

    def address_location_lines(self): 

        #~ lines = [] 

        #~ lines = [self.name] 

        af = get_address_formatter(self.country) 

 

        if self.addr1: 

            yield self.addr1 

        for ln in af.get_street_lines(self): 

            yield ln 

        if self.addr2: 

            yield self.addr2 

 

        for ln in af.get_city_lines(self): 

            yield ln 

 

        if self.country is not None: 

            kodu = dd.plugins.countries.get_my_country() 

            if kodu is None or self.country != kodu: 

                # (if self.country != sender's country) 

                yield str(self.country) 

 

        #~ logger.debug('%s : as_address() -> %r',self,lines) 

 

    def address_location(self, linesep="\n"): 

        """Return the plain text postal address location part.  Lines are 

        separated by `linesep` which defaults to ``"\\n"``. 

 

        The following example creates a Partner, then calls its 

        :meth:`address_location` method: 

 

        >>> BE = countries.Country.objects.get(pk='BE') 

        >>> p = contacts.Partner( 

        ...   name="Foo", 

        ...   street_prefix="Rue de l'", street="Abattoir",  

        ...   street_no=5, country=BE, zip_code="4000") 

        >>> p.full_clean() 

        >>> p.save() 

        >>> print(p.address_location()) 

        Rue de l' Abattoir 5 

        4000 Liège 

        Belgium 

 

        """ 

        return linesep.join(self.address_location_lines()) 

 

    @dd.displayfield(_("Address")) 

    def address_column(self, ar): 

        return self.address_location(', ')