summaryrefslogtreecommitdiff
path: root/python-django-relativity.spec
blob: c94eedb093df8838ccf2bf66de767375fa6a56f0 (plain)
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
%global _empty_manifest_terminate_build 0
Name:		python-django-relativity
Version:	0.2.6
Release:	1
Summary:	A flexible relationship field for the Django ORM.
License:	BSD License
URL:		https://github.com/alexhill/django-relativity
Source0:	https://mirrors.nju.edu.cn/pypi/web/packages/ee/93/e02dc850fc04d9925e830812d201c92e4ef29c9bdf12d47bd67cd102a8c8/django-relativity-0.2.6.tar.gz
BuildArch:	noarch

Requires:	python3-django

%description
# django-relativity

[![PyPI](https://img.shields.io/pypi/v/django-relativity.svg)](https://pypi.org/project/django-relativity/)
[![Build Status](https://app.travis-ci.com/AlexHill/django-relativity.svg?branch=master)](https://app.travis-ci.com/github/AlexHill/django-relativity)

django-relativity provides a `Relationship` field that lets you declare the non-foreign-key relationships between your models and use them throughout the ORM.

_Non-foreign-key relationships?_

Like the relationship between a node and its descendants in a tree, or between two tagged items that share a tag. Almost anything you can express with Django's filter syntax, you can use to define a relationship.

_Use them throughout the ORM?_

Yes, across joins, in `filter()`, in methods like `prefetch_related()` or `values()` - anywhere Django expects to see a field.

## What problem does this solve?

Sometimes the relationships between our models are more complex than equality between a primary key field on one model and a foreign key on another.

For example: when working with trees, we very often need to find a given node's descendants - its children, their children, and so on. The exact query we have to run depends on how we've chosen to implement our tree structure at the database level, and fortunately there are mature libraries available to take care of that for us. [django-mptt](http://django-mptt.readthedocs.io/en/latest/models.html#get-descendants-include-self-false) and [django-treebeard](http://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.get_descendants) both provide methods called `get_descendants()` for exactly this purpose. These return a queryset selecting the node's descendants, which we can then filter further, or use as an argument to another filter, and so on. So what's the problem?

The problem is that the node-descendants relationship is invisible to the Django ORM. We can't filter against it, like `Node.objects.filter(descendants__in=objs)`. We can't traverse it, like `Node.objects.filter(descendants__name__startswith="A")`. We can't prefetch it. None of the niceties that Django provides for working with relationships are available for us to use with this relationship, because it can't be declared as a `ManyToManyField` or `ForeignKey`.

django-relativity lets all those ORM features work with almost any kind of relationship you can dream up.

### MPTT and treebeard helpers

If you use django-mptt or django-treebeard and you want to jump right in, relativity comes with fields to select a node's descendants and its subtree (which respectively exclude and include the current node). The default reverse relation names for these fields are `ascendants` and `rootpath`.

```python
# For django-mptt
from relativity.mptt import MPTTDescendants, MPTTSubtree

# for treebeard with materialised path
from relativity.treebeard import MP_Descendants, MP_Subtree

# for treebeard with nested sets
from relativity.treebeard import NS_Descendants, NS_Subtree


class TreeNode(MPTTModel):
    ...
    
    # after defining all your other fields, including TreeForeignKey...
    descendants = MPTTDescendants()
    subtree = MPTTSubtree()
```

## What does the code look like?

Here are some models for an imaginary website about chemistry, where users can filter compounds by regular expression and save their searches:

```python
from relativity.fields import L, Relationship

class Chemical(Model):
    common_name = TextField()
    chemical_name = TextField()
    formula = TextField()

class SavedFilter(Model):
    user = ForeignKey(User)
    search_regex = TextField()
    chemicals = Relationship(
        to=Chemical,
        predicate=Q(formula__regex=L('search_regex')),
    )
```

Now I can use that field like this:

```python
my_filter.chemicals.all()  # all the chemicals whose formulae match this filter
my_chemical.saved_filters.all()  # all the filters whose regexps match this chemical
my_user.filter(saved_filters__chemicals=my_chemical)  # users with filters matching this chemical
my_chemical.filter(saved_filters__user=my_user)  # chemicals in any of this user's filters
```

In short, I can use it just like a normal Django relation. It provides forward and reverse properties that return Managers, and I can use it in filters spanning multiple models.

_How does that `Relationship` field work?_

A `Relationship` behaves like a `ForeignKey` or `ManyToManyField` and defines a relationship with another model. Unlike the built-in Django relations, `Relationship` doesn't use its own database column or table to determine which instances are related. Instead, you give it an arbitrary _predicate_, expressed as a normal Django `Q`, which determines which instances of the `to` model are in the relationship.

_What's that `L` doing there?_

In Django ORM expressions, `F` is a reference to a field on the model being queried. `L` is similar, but refers to a field on the model on which the `Relationship` is defined. Think of it as L for the _left-hand_ side of a join, or L for the _local_ model.

Going back to our example - the `chemicals` field provides the set of `Chemical`s whose formulae match the `SavedFilter`'s regular expression.

Let's make some chemicals:

```python
>>> Chemical.objects.create(name="baking soda", formula="NaHCO3")
... Chemical.objects.create(common_name="freon",  formula="CF2Cl2")
... Chemical.objects.create(common_name="grain alcohol", formula="C2H5OH")
... Chemical.objects.create(common_name="quartz", formula="SiO2")
... Chemical.objects.create(common_name="salt", formula="NaCl")
```

Now, say I'm a user who's interested in chemicals containing chlorine. Simple enough:

```python
>>> chloriney = SavedFilter.objects.create(user=alex, search_regex=r'Cl')
>>> chloriney.compounds.all()
<QuerySet [<Chemical: CF2Cl2>, <Chemical: NaCl>]>
```

Anne is interested in oxides, so her regex is a bit more complicated:

```python
>>> oxides = SavedFilter.objects.create(user=anne, search_regex=r'([A-Z][a-z]?\d*)O(\d+|(?!H))')
>>> oxides.compounds.all()
<QuerySet [<Chemical: NaHCO3>, <Chemical: SiO2>]>
```

Now, this is nothing you couldn't do with a helper method on `SavedFilter` which returns the appropriate QuerySet. But now we add a new chemical to our database, and we want to identify users who are interested in that chemical so we can notify them:

```python
>>> added_chemical = Chemical.objects.create(common_name="chlorine dioxide", chemical_name="chlorine dioxide", formula="ClO2")
<Chemical: ClO2>
>>> User.objects.filter(saved_filters__chemicals=added_chemical)
<QuerySet [<User: alex>, <User: anne>]>
```

This is why I call django-relativity a _force-multiplier_ for the ORM. `Relationship`s work with the ORM just like `ForeignKey`s or `ManyToManyField`s. You can traverse them and filter on them just like you can with the built-in relationship fields. The goal of django-relativity is for `Relationship`s to be able to do anything a normal Django relationship field can do.

### Reverse relations

`Relationship`s work in the reverse direction as well, with the same naming behaviour as Django's fields: the default related name is `<model_name>_set` or `<model_name>` depending on arity, overridable with the `related_name` argument. `related_query_name` works as well.

In the example above, `my_chemical.saved_filter_set.all()` will return all of the `SavedFilter`s matching `my_chemical`. `Chemical.objects.filter(saved_filters__user=alex)` will select all of the chemicals in all of my saved filters.

### Arity

Relationships between models can be one-to-one, one-to-many, many-to-one, or many-to-many. `Relationship` can express all of those, using the `multiple` and `reverse_multiple` arguments. Both default to `True`.

Here's a many-to-one example - many cart items can be associated with each product, but only one product is associated with each cart item.

```python
class CartItem(models.Model):
    product_code = models.TextField()
    product = Relationship(
        to=Product,
        predicate=Q(sku=L('product_code')),
        multiple=False,
    )
```

## What state is this project in?

This project is used in production and in active development. Things not covered by the tests have every chance of not working.


## Migrating from relativity < 0.2.0

Before 0.2.0, it was necessary to import a backported version of `django.db.models.Q` from `relativity.compat` in order to make migrations work in Django 1.11. From 0.2.0 onwards, that's no longer necessary. The backported `Q` still exists as an alias to django.db.models, but a DeprecationWarning will be issued on import. You should replace all uses with Django's standard `Q`.

From 0.2.0 onwards, relativity's fields do not generate migrations. When migrating from older versions, I recommend simply removing all references to relativity's fields from the original migrations that created them, rather than generating new migrations removing the virtual fields.


%package -n python3-django-relativity
Summary:	A flexible relationship field for the Django ORM.
Provides:	python-django-relativity
BuildRequires:	python3-devel
BuildRequires:	python3-setuptools
BuildRequires:	python3-pip
%description -n python3-django-relativity
# django-relativity

[![PyPI](https://img.shields.io/pypi/v/django-relativity.svg)](https://pypi.org/project/django-relativity/)
[![Build Status](https://app.travis-ci.com/AlexHill/django-relativity.svg?branch=master)](https://app.travis-ci.com/github/AlexHill/django-relativity)

django-relativity provides a `Relationship` field that lets you declare the non-foreign-key relationships between your models and use them throughout the ORM.

_Non-foreign-key relationships?_

Like the relationship between a node and its descendants in a tree, or between two tagged items that share a tag. Almost anything you can express with Django's filter syntax, you can use to define a relationship.

_Use them throughout the ORM?_

Yes, across joins, in `filter()`, in methods like `prefetch_related()` or `values()` - anywhere Django expects to see a field.

## What problem does this solve?

Sometimes the relationships between our models are more complex than equality between a primary key field on one model and a foreign key on another.

For example: when working with trees, we very often need to find a given node's descendants - its children, their children, and so on. The exact query we have to run depends on how we've chosen to implement our tree structure at the database level, and fortunately there are mature libraries available to take care of that for us. [django-mptt](http://django-mptt.readthedocs.io/en/latest/models.html#get-descendants-include-self-false) and [django-treebeard](http://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.get_descendants) both provide methods called `get_descendants()` for exactly this purpose. These return a queryset selecting the node's descendants, which we can then filter further, or use as an argument to another filter, and so on. So what's the problem?

The problem is that the node-descendants relationship is invisible to the Django ORM. We can't filter against it, like `Node.objects.filter(descendants__in=objs)`. We can't traverse it, like `Node.objects.filter(descendants__name__startswith="A")`. We can't prefetch it. None of the niceties that Django provides for working with relationships are available for us to use with this relationship, because it can't be declared as a `ManyToManyField` or `ForeignKey`.

django-relativity lets all those ORM features work with almost any kind of relationship you can dream up.

### MPTT and treebeard helpers

If you use django-mptt or django-treebeard and you want to jump right in, relativity comes with fields to select a node's descendants and its subtree (which respectively exclude and include the current node). The default reverse relation names for these fields are `ascendants` and `rootpath`.

```python
# For django-mptt
from relativity.mptt import MPTTDescendants, MPTTSubtree

# for treebeard with materialised path
from relativity.treebeard import MP_Descendants, MP_Subtree

# for treebeard with nested sets
from relativity.treebeard import NS_Descendants, NS_Subtree


class TreeNode(MPTTModel):
    ...
    
    # after defining all your other fields, including TreeForeignKey...
    descendants = MPTTDescendants()
    subtree = MPTTSubtree()
```

## What does the code look like?

Here are some models for an imaginary website about chemistry, where users can filter compounds by regular expression and save their searches:

```python
from relativity.fields import L, Relationship

class Chemical(Model):
    common_name = TextField()
    chemical_name = TextField()
    formula = TextField()

class SavedFilter(Model):
    user = ForeignKey(User)
    search_regex = TextField()
    chemicals = Relationship(
        to=Chemical,
        predicate=Q(formula__regex=L('search_regex')),
    )
```

Now I can use that field like this:

```python
my_filter.chemicals.all()  # all the chemicals whose formulae match this filter
my_chemical.saved_filters.all()  # all the filters whose regexps match this chemical
my_user.filter(saved_filters__chemicals=my_chemical)  # users with filters matching this chemical
my_chemical.filter(saved_filters__user=my_user)  # chemicals in any of this user's filters
```

In short, I can use it just like a normal Django relation. It provides forward and reverse properties that return Managers, and I can use it in filters spanning multiple models.

_How does that `Relationship` field work?_

A `Relationship` behaves like a `ForeignKey` or `ManyToManyField` and defines a relationship with another model. Unlike the built-in Django relations, `Relationship` doesn't use its own database column or table to determine which instances are related. Instead, you give it an arbitrary _predicate_, expressed as a normal Django `Q`, which determines which instances of the `to` model are in the relationship.

_What's that `L` doing there?_

In Django ORM expressions, `F` is a reference to a field on the model being queried. `L` is similar, but refers to a field on the model on which the `Relationship` is defined. Think of it as L for the _left-hand_ side of a join, or L for the _local_ model.

Going back to our example - the `chemicals` field provides the set of `Chemical`s whose formulae match the `SavedFilter`'s regular expression.

Let's make some chemicals:

```python
>>> Chemical.objects.create(name="baking soda", formula="NaHCO3")
... Chemical.objects.create(common_name="freon",  formula="CF2Cl2")
... Chemical.objects.create(common_name="grain alcohol", formula="C2H5OH")
... Chemical.objects.create(common_name="quartz", formula="SiO2")
... Chemical.objects.create(common_name="salt", formula="NaCl")
```

Now, say I'm a user who's interested in chemicals containing chlorine. Simple enough:

```python
>>> chloriney = SavedFilter.objects.create(user=alex, search_regex=r'Cl')
>>> chloriney.compounds.all()
<QuerySet [<Chemical: CF2Cl2>, <Chemical: NaCl>]>
```

Anne is interested in oxides, so her regex is a bit more complicated:

```python
>>> oxides = SavedFilter.objects.create(user=anne, search_regex=r'([A-Z][a-z]?\d*)O(\d+|(?!H))')
>>> oxides.compounds.all()
<QuerySet [<Chemical: NaHCO3>, <Chemical: SiO2>]>
```

Now, this is nothing you couldn't do with a helper method on `SavedFilter` which returns the appropriate QuerySet. But now we add a new chemical to our database, and we want to identify users who are interested in that chemical so we can notify them:

```python
>>> added_chemical = Chemical.objects.create(common_name="chlorine dioxide", chemical_name="chlorine dioxide", formula="ClO2")
<Chemical: ClO2>
>>> User.objects.filter(saved_filters__chemicals=added_chemical)
<QuerySet [<User: alex>, <User: anne>]>
```

This is why I call django-relativity a _force-multiplier_ for the ORM. `Relationship`s work with the ORM just like `ForeignKey`s or `ManyToManyField`s. You can traverse them and filter on them just like you can with the built-in relationship fields. The goal of django-relativity is for `Relationship`s to be able to do anything a normal Django relationship field can do.

### Reverse relations

`Relationship`s work in the reverse direction as well, with the same naming behaviour as Django's fields: the default related name is `<model_name>_set` or `<model_name>` depending on arity, overridable with the `related_name` argument. `related_query_name` works as well.

In the example above, `my_chemical.saved_filter_set.all()` will return all of the `SavedFilter`s matching `my_chemical`. `Chemical.objects.filter(saved_filters__user=alex)` will select all of the chemicals in all of my saved filters.

### Arity

Relationships between models can be one-to-one, one-to-many, many-to-one, or many-to-many. `Relationship` can express all of those, using the `multiple` and `reverse_multiple` arguments. Both default to `True`.

Here's a many-to-one example - many cart items can be associated with each product, but only one product is associated with each cart item.

```python
class CartItem(models.Model):
    product_code = models.TextField()
    product = Relationship(
        to=Product,
        predicate=Q(sku=L('product_code')),
        multiple=False,
    )
```

## What state is this project in?

This project is used in production and in active development. Things not covered by the tests have every chance of not working.


## Migrating from relativity < 0.2.0

Before 0.2.0, it was necessary to import a backported version of `django.db.models.Q` from `relativity.compat` in order to make migrations work in Django 1.11. From 0.2.0 onwards, that's no longer necessary. The backported `Q` still exists as an alias to django.db.models, but a DeprecationWarning will be issued on import. You should replace all uses with Django's standard `Q`.

From 0.2.0 onwards, relativity's fields do not generate migrations. When migrating from older versions, I recommend simply removing all references to relativity's fields from the original migrations that created them, rather than generating new migrations removing the virtual fields.


%package help
Summary:	Development documents and examples for django-relativity
Provides:	python3-django-relativity-doc
%description help
# django-relativity

[![PyPI](https://img.shields.io/pypi/v/django-relativity.svg)](https://pypi.org/project/django-relativity/)
[![Build Status](https://app.travis-ci.com/AlexHill/django-relativity.svg?branch=master)](https://app.travis-ci.com/github/AlexHill/django-relativity)

django-relativity provides a `Relationship` field that lets you declare the non-foreign-key relationships between your models and use them throughout the ORM.

_Non-foreign-key relationships?_

Like the relationship between a node and its descendants in a tree, or between two tagged items that share a tag. Almost anything you can express with Django's filter syntax, you can use to define a relationship.

_Use them throughout the ORM?_

Yes, across joins, in `filter()`, in methods like `prefetch_related()` or `values()` - anywhere Django expects to see a field.

## What problem does this solve?

Sometimes the relationships between our models are more complex than equality between a primary key field on one model and a foreign key on another.

For example: when working with trees, we very often need to find a given node's descendants - its children, their children, and so on. The exact query we have to run depends on how we've chosen to implement our tree structure at the database level, and fortunately there are mature libraries available to take care of that for us. [django-mptt](http://django-mptt.readthedocs.io/en/latest/models.html#get-descendants-include-self-false) and [django-treebeard](http://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.get_descendants) both provide methods called `get_descendants()` for exactly this purpose. These return a queryset selecting the node's descendants, which we can then filter further, or use as an argument to another filter, and so on. So what's the problem?

The problem is that the node-descendants relationship is invisible to the Django ORM. We can't filter against it, like `Node.objects.filter(descendants__in=objs)`. We can't traverse it, like `Node.objects.filter(descendants__name__startswith="A")`. We can't prefetch it. None of the niceties that Django provides for working with relationships are available for us to use with this relationship, because it can't be declared as a `ManyToManyField` or `ForeignKey`.

django-relativity lets all those ORM features work with almost any kind of relationship you can dream up.

### MPTT and treebeard helpers

If you use django-mptt or django-treebeard and you want to jump right in, relativity comes with fields to select a node's descendants and its subtree (which respectively exclude and include the current node). The default reverse relation names for these fields are `ascendants` and `rootpath`.

```python
# For django-mptt
from relativity.mptt import MPTTDescendants, MPTTSubtree

# for treebeard with materialised path
from relativity.treebeard import MP_Descendants, MP_Subtree

# for treebeard with nested sets
from relativity.treebeard import NS_Descendants, NS_Subtree


class TreeNode(MPTTModel):
    ...
    
    # after defining all your other fields, including TreeForeignKey...
    descendants = MPTTDescendants()
    subtree = MPTTSubtree()
```

## What does the code look like?

Here are some models for an imaginary website about chemistry, where users can filter compounds by regular expression and save their searches:

```python
from relativity.fields import L, Relationship

class Chemical(Model):
    common_name = TextField()
    chemical_name = TextField()
    formula = TextField()

class SavedFilter(Model):
    user = ForeignKey(User)
    search_regex = TextField()
    chemicals = Relationship(
        to=Chemical,
        predicate=Q(formula__regex=L('search_regex')),
    )
```

Now I can use that field like this:

```python
my_filter.chemicals.all()  # all the chemicals whose formulae match this filter
my_chemical.saved_filters.all()  # all the filters whose regexps match this chemical
my_user.filter(saved_filters__chemicals=my_chemical)  # users with filters matching this chemical
my_chemical.filter(saved_filters__user=my_user)  # chemicals in any of this user's filters
```

In short, I can use it just like a normal Django relation. It provides forward and reverse properties that return Managers, and I can use it in filters spanning multiple models.

_How does that `Relationship` field work?_

A `Relationship` behaves like a `ForeignKey` or `ManyToManyField` and defines a relationship with another model. Unlike the built-in Django relations, `Relationship` doesn't use its own database column or table to determine which instances are related. Instead, you give it an arbitrary _predicate_, expressed as a normal Django `Q`, which determines which instances of the `to` model are in the relationship.

_What's that `L` doing there?_

In Django ORM expressions, `F` is a reference to a field on the model being queried. `L` is similar, but refers to a field on the model on which the `Relationship` is defined. Think of it as L for the _left-hand_ side of a join, or L for the _local_ model.

Going back to our example - the `chemicals` field provides the set of `Chemical`s whose formulae match the `SavedFilter`'s regular expression.

Let's make some chemicals:

```python
>>> Chemical.objects.create(name="baking soda", formula="NaHCO3")
... Chemical.objects.create(common_name="freon",  formula="CF2Cl2")
... Chemical.objects.create(common_name="grain alcohol", formula="C2H5OH")
... Chemical.objects.create(common_name="quartz", formula="SiO2")
... Chemical.objects.create(common_name="salt", formula="NaCl")
```

Now, say I'm a user who's interested in chemicals containing chlorine. Simple enough:

```python
>>> chloriney = SavedFilter.objects.create(user=alex, search_regex=r'Cl')
>>> chloriney.compounds.all()
<QuerySet [<Chemical: CF2Cl2>, <Chemical: NaCl>]>
```

Anne is interested in oxides, so her regex is a bit more complicated:

```python
>>> oxides = SavedFilter.objects.create(user=anne, search_regex=r'([A-Z][a-z]?\d*)O(\d+|(?!H))')
>>> oxides.compounds.all()
<QuerySet [<Chemical: NaHCO3>, <Chemical: SiO2>]>
```

Now, this is nothing you couldn't do with a helper method on `SavedFilter` which returns the appropriate QuerySet. But now we add a new chemical to our database, and we want to identify users who are interested in that chemical so we can notify them:

```python
>>> added_chemical = Chemical.objects.create(common_name="chlorine dioxide", chemical_name="chlorine dioxide", formula="ClO2")
<Chemical: ClO2>
>>> User.objects.filter(saved_filters__chemicals=added_chemical)
<QuerySet [<User: alex>, <User: anne>]>
```

This is why I call django-relativity a _force-multiplier_ for the ORM. `Relationship`s work with the ORM just like `ForeignKey`s or `ManyToManyField`s. You can traverse them and filter on them just like you can with the built-in relationship fields. The goal of django-relativity is for `Relationship`s to be able to do anything a normal Django relationship field can do.

### Reverse relations

`Relationship`s work in the reverse direction as well, with the same naming behaviour as Django's fields: the default related name is `<model_name>_set` or `<model_name>` depending on arity, overridable with the `related_name` argument. `related_query_name` works as well.

In the example above, `my_chemical.saved_filter_set.all()` will return all of the `SavedFilter`s matching `my_chemical`. `Chemical.objects.filter(saved_filters__user=alex)` will select all of the chemicals in all of my saved filters.

### Arity

Relationships between models can be one-to-one, one-to-many, many-to-one, or many-to-many. `Relationship` can express all of those, using the `multiple` and `reverse_multiple` arguments. Both default to `True`.

Here's a many-to-one example - many cart items can be associated with each product, but only one product is associated with each cart item.

```python
class CartItem(models.Model):
    product_code = models.TextField()
    product = Relationship(
        to=Product,
        predicate=Q(sku=L('product_code')),
        multiple=False,
    )
```

## What state is this project in?

This project is used in production and in active development. Things not covered by the tests have every chance of not working.


## Migrating from relativity < 0.2.0

Before 0.2.0, it was necessary to import a backported version of `django.db.models.Q` from `relativity.compat` in order to make migrations work in Django 1.11. From 0.2.0 onwards, that's no longer necessary. The backported `Q` still exists as an alias to django.db.models, but a DeprecationWarning will be issued on import. You should replace all uses with Django's standard `Q`.

From 0.2.0 onwards, relativity's fields do not generate migrations. When migrating from older versions, I recommend simply removing all references to relativity's fields from the original migrations that created them, rather than generating new migrations removing the virtual fields.


%prep
%autosetup -n django-relativity-0.2.6

%build
%py3_build

%install
%py3_install
install -d -m755 %{buildroot}/%{_pkgdocdir}
if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
pushd %{buildroot}
if [ -d usr/lib ]; then
	find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/lib64 ]; then
	find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/bin ]; then
	find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/sbin ]; then
	find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
fi
touch doclist.lst
if [ -d usr/share/man ]; then
	find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
fi
popd
mv %{buildroot}/filelist.lst .
mv %{buildroot}/doclist.lst .

%files -n python3-django-relativity -f filelist.lst
%dir %{python3_sitelib}/*

%files help -f doclist.lst
%{_docdir}/*

%changelog
* Mon May 15 2023 Python_Bot <Python_Bot@openeuler.org> - 0.2.6-1
- Package Spec generated