1from collections import OrderedDict, defaultdict
2from textwrap import dedent
3
4import pytest
5from django.db import models
6from mock import patch
7
8from graphene import Connection, Field, Interface, ObjectType, Schema, String
9from graphene.relay import Node
10
11from .. import registry
12from ..filter import DjangoFilterConnectionField
13from ..types import DjangoObjectType, DjangoObjectTypeOptions
14from .models import Article as ArticleModel
15from .models import Reporter as ReporterModel
16
17
18class Reporter(DjangoObjectType):
19    """Reporter description"""
20
21    class Meta:
22        model = ReporterModel
23
24
25class ArticleConnection(Connection):
26    """Article Connection"""
27
28    test = String()
29
30    def resolve_test():
31        return "test"
32
33    class Meta:
34        abstract = True
35
36
37class Article(DjangoObjectType):
38    """Article description"""
39
40    class Meta:
41        model = ArticleModel
42        interfaces = (Node,)
43        connection_class = ArticleConnection
44
45
46class RootQuery(ObjectType):
47    node = Node.Field()
48
49
50schema = Schema(query=RootQuery, types=[Article, Reporter])
51
52
53def test_django_interface():
54    assert issubclass(Node, Interface)
55    assert issubclass(Node, Node)
56
57
58@patch("graphene_django.tests.models.Article.objects.get", return_value=Article(id=1))
59def test_django_get_node(get):
60    article = Article.get_node(None, 1)
61    get.assert_called_with(pk=1)
62    assert article.id == 1
63
64
65def test_django_objecttype_map_correct_fields():
66    fields = Reporter._meta.fields
67    fields = list(fields.keys())
68    assert fields[:-2] == [
69        "id",
70        "first_name",
71        "last_name",
72        "email",
73        "pets",
74        "a_choice",
75        "reporter_type",
76    ]
77    assert sorted(fields[-2:]) == ["articles", "films"]
78
79
80def test_django_objecttype_with_node_have_correct_fields():
81    fields = Article._meta.fields
82    assert list(fields.keys()) == [
83        "id",
84        "headline",
85        "pub_date",
86        "pub_date_time",
87        "reporter",
88        "editor",
89        "lang",
90        "importance",
91    ]
92
93
94def test_django_objecttype_with_custom_meta():
95    class ArticleTypeOptions(DjangoObjectTypeOptions):
96        """Article Type Options"""
97
98    class ArticleType(DjangoObjectType):
99        class Meta:
100            abstract = True
101
102        @classmethod
103        def __init_subclass_with_meta__(cls, **options):
104            options.setdefault("_meta", ArticleTypeOptions(cls))
105            super(ArticleType, cls).__init_subclass_with_meta__(**options)
106
107    class Article(ArticleType):
108        class Meta:
109            model = ArticleModel
110
111    assert isinstance(Article._meta, ArticleTypeOptions)
112
113
114def test_schema_representation():
115    expected = """
116schema {
117  query: RootQuery
118}
119
120type Article implements Node {
121  id: ID!
122  headline: String!
123  pubDate: Date!
124  pubDateTime: DateTime!
125  reporter: Reporter!
126  editor: Reporter!
127  lang: ArticleLang!
128  importance: ArticleImportance
129}
130
131type ArticleConnection {
132  pageInfo: PageInfo!
133  edges: [ArticleEdge]!
134  test: String
135}
136
137type ArticleEdge {
138  node: Article
139  cursor: String!
140}
141
142enum ArticleImportance {
143  A_1
144  A_2
145}
146
147enum ArticleLang {
148  ES
149  EN
150}
151
152scalar Date
153
154scalar DateTime
155
156interface Node {
157  id: ID!
158}
159
160type PageInfo {
161  hasNextPage: Boolean!
162  hasPreviousPage: Boolean!
163  startCursor: String
164  endCursor: String
165}
166
167type Reporter {
168  id: ID!
169  firstName: String!
170  lastName: String!
171  email: String!
172  pets: [Reporter!]!
173  aChoice: ReporterAChoice
174  reporterType: ReporterReporterType
175  articles(offset: Int, before: String, after: String, first: Int, last: Int): ArticleConnection!
176}
177
178enum ReporterAChoice {
179  A_1
180  A_2
181}
182
183enum ReporterReporterType {
184  A_1
185  A_2
186}
187
188type RootQuery {
189  node(id: ID!): Node
190}
191""".lstrip()
192    assert str(schema) == expected
193
194
195def with_local_registry(func):
196    def inner(*args, **kwargs):
197        old = registry.get_global_registry()
198        try:
199            retval = func(*args, **kwargs)
200        except Exception as e:
201            registry.registry = old
202            raise e
203        else:
204            registry.registry = old
205            return retval
206
207    return inner
208
209
210@with_local_registry
211def test_django_objecttype_only_fields():
212    with pytest.warns(PendingDeprecationWarning):
213
214        class Reporter(DjangoObjectType):
215            class Meta:
216                model = ReporterModel
217                only_fields = ("id", "email", "films")
218
219    fields = list(Reporter._meta.fields.keys())
220    assert fields == ["id", "email", "films"]
221
222
223@with_local_registry
224def test_django_objecttype_fields():
225    class Reporter(DjangoObjectType):
226        class Meta:
227            model = ReporterModel
228            fields = ("id", "email", "films")
229
230    fields = list(Reporter._meta.fields.keys())
231    assert fields == ["id", "email", "films"]
232
233
234@with_local_registry
235def test_django_objecttype_fields_empty():
236    class Reporter(DjangoObjectType):
237        class Meta:
238            model = ReporterModel
239            fields = ()
240
241    fields = list(Reporter._meta.fields.keys())
242    assert fields == []
243
244
245@with_local_registry
246def test_django_objecttype_only_fields_and_fields():
247    with pytest.raises(Exception):
248
249        class Reporter(DjangoObjectType):
250            class Meta:
251                model = ReporterModel
252                only_fields = ("id", "email", "films")
253                fields = ("id", "email", "films")
254
255
256@with_local_registry
257def test_django_objecttype_all_fields():
258    class Reporter(DjangoObjectType):
259        class Meta:
260            model = ReporterModel
261            fields = "__all__"
262
263    fields = list(Reporter._meta.fields.keys())
264    assert len(fields) == len(ReporterModel._meta.get_fields())
265
266
267@with_local_registry
268def test_django_objecttype_exclude_fields():
269    with pytest.warns(PendingDeprecationWarning):
270
271        class Reporter(DjangoObjectType):
272            class Meta:
273                model = ReporterModel
274                exclude_fields = ["email"]
275
276    fields = list(Reporter._meta.fields.keys())
277    assert "email" not in fields
278
279
280@with_local_registry
281def test_django_objecttype_exclude():
282    class Reporter(DjangoObjectType):
283        class Meta:
284            model = ReporterModel
285            exclude = ["email"]
286
287    fields = list(Reporter._meta.fields.keys())
288    assert "email" not in fields
289
290
291@with_local_registry
292def test_django_objecttype_exclude_fields_and_exclude():
293    with pytest.raises(Exception):
294
295        class Reporter(DjangoObjectType):
296            class Meta:
297                model = ReporterModel
298                exclude = ["email"]
299                exclude_fields = ["email"]
300
301
302@with_local_registry
303def test_django_objecttype_exclude_and_only():
304    with pytest.raises(AssertionError):
305
306        class Reporter(DjangoObjectType):
307            class Meta:
308                model = ReporterModel
309                exclude = ["email"]
310                fields = ["id"]
311
312
313@with_local_registry
314def test_django_objecttype_fields_exclude_type_checking():
315    with pytest.raises(TypeError):
316
317        class Reporter(DjangoObjectType):
318            class Meta:
319                model = ReporterModel
320                fields = "foo"
321
322    with pytest.raises(TypeError):
323
324        class Reporter2(DjangoObjectType):
325            class Meta:
326                model = ReporterModel
327                exclude = "foo"
328
329
330@with_local_registry
331def test_django_objecttype_fields_exist_on_model():
332    with pytest.warns(UserWarning, match=r"Field name .* doesn't exist"):
333
334        class Reporter(DjangoObjectType):
335            class Meta:
336                model = ReporterModel
337                fields = ["first_name", "foo", "email"]
338
339    with pytest.warns(
340        UserWarning,
341        match=r"Field name .* matches an attribute on Django model .* but it's not a model field",
342    ) as record:
343
344        class Reporter2(DjangoObjectType):
345            class Meta:
346                model = ReporterModel
347                fields = ["first_name", "some_method", "email"]
348
349    # Don't warn if selecting a custom field
350    with pytest.warns(None) as record:
351
352        class Reporter3(DjangoObjectType):
353            custom_field = String()
354
355            class Meta:
356                model = ReporterModel
357                fields = ["first_name", "custom_field", "email"]
358
359    assert len(record) == 0
360
361
362@with_local_registry
363def test_django_objecttype_exclude_fields_exist_on_model():
364    with pytest.warns(
365        UserWarning,
366        match=r"Django model .* does not have a field or attribute named .*",
367    ):
368
369        class Reporter(DjangoObjectType):
370            class Meta:
371                model = ReporterModel
372                exclude = ["foo"]
373
374    # Don't warn if selecting a custom field
375    with pytest.warns(
376        UserWarning,
377        match=r"Excluding the custom field .* on DjangoObjectType .* has no effect.",
378    ):
379
380        class Reporter3(DjangoObjectType):
381            custom_field = String()
382
383            class Meta:
384                model = ReporterModel
385                exclude = ["custom_field"]
386
387    # Don't warn on exclude fields
388    with pytest.warns(None) as record:
389
390        class Reporter4(DjangoObjectType):
391            class Meta:
392                model = ReporterModel
393                exclude = ["email", "first_name"]
394
395    assert len(record) == 0
396
397
398def custom_enum_name(field):
399    return "CustomEnum{}".format(field.name.title())
400
401
402class TestDjangoObjectType:
403    @pytest.fixture
404    def PetModel(self):
405        class PetModel(models.Model):
406            kind = models.CharField(choices=(("cat", "Cat"), ("dog", "Dog")))
407            cuteness = models.IntegerField(
408                choices=((1, "Kind of cute"), (2, "Pretty cute"), (3, "OMG SO CUTE!!!"))
409            )
410
411        yield PetModel
412
413        # Clear Django model cache so we don't get warnings when creating the
414        # model multiple times
415        PetModel._meta.apps.all_models = defaultdict(OrderedDict)
416
417    def test_django_objecttype_convert_choices_enum_false(self, PetModel):
418        class Pet(DjangoObjectType):
419            class Meta:
420                model = PetModel
421                convert_choices_to_enum = False
422
423        class Query(ObjectType):
424            pet = Field(Pet)
425
426        schema = Schema(query=Query)
427
428        assert str(schema) == dedent(
429            """\
430        schema {
431          query: Query
432        }
433
434        type Pet {
435          id: ID!
436          kind: String!
437          cuteness: Int!
438        }
439
440        type Query {
441          pet: Pet
442        }
443        """
444        )
445
446    def test_django_objecttype_convert_choices_enum_list(self, PetModel):
447        class Pet(DjangoObjectType):
448            class Meta:
449                model = PetModel
450                convert_choices_to_enum = ["kind"]
451
452        class Query(ObjectType):
453            pet = Field(Pet)
454
455        schema = Schema(query=Query)
456
457        assert str(schema) == dedent(
458            """\
459        schema {
460          query: Query
461        }
462
463        type Pet {
464          id: ID!
465          kind: PetModelKind!
466          cuteness: Int!
467        }
468
469        enum PetModelKind {
470          CAT
471          DOG
472        }
473
474        type Query {
475          pet: Pet
476        }
477        """
478        )
479
480    def test_django_objecttype_convert_choices_enum_empty_list(self, PetModel):
481        class Pet(DjangoObjectType):
482            class Meta:
483                model = PetModel
484                convert_choices_to_enum = []
485
486        class Query(ObjectType):
487            pet = Field(Pet)
488
489        schema = Schema(query=Query)
490
491        assert str(schema) == dedent(
492            """\
493        schema {
494          query: Query
495        }
496
497        type Pet {
498          id: ID!
499          kind: String!
500          cuteness: Int!
501        }
502
503        type Query {
504          pet: Pet
505        }
506        """
507        )
508
509    def test_django_objecttype_convert_choices_enum_naming_collisions(
510        self, PetModel, graphene_settings
511    ):
512        graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
513
514        class PetModelKind(DjangoObjectType):
515            class Meta:
516                model = PetModel
517                fields = ["id", "kind"]
518
519        class Query(ObjectType):
520            pet = Field(PetModelKind)
521
522        schema = Schema(query=Query)
523
524        assert str(schema) == dedent(
525            """\
526        schema {
527          query: Query
528        }
529
530        type PetModelKind {
531          id: ID!
532          kind: TestsPetModelKindChoices!
533        }
534
535        type Query {
536          pet: PetModelKind
537        }
538
539        enum TestsPetModelKindChoices {
540          CAT
541          DOG
542        }
543        """
544        )
545
546    def test_django_objecttype_choices_custom_enum_name(
547        self, PetModel, graphene_settings
548    ):
549        graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = (
550            "graphene_django.tests.test_types.custom_enum_name"
551        )
552
553        class PetModelKind(DjangoObjectType):
554            class Meta:
555                model = PetModel
556                fields = ["id", "kind"]
557
558        class Query(ObjectType):
559            pet = Field(PetModelKind)
560
561        schema = Schema(query=Query)
562
563        assert str(schema) == dedent(
564            """\
565        schema {
566          query: Query
567        }
568
569        enum CustomEnumKind {
570          CAT
571          DOG
572        }
573
574        type PetModelKind {
575          id: ID!
576          kind: CustomEnumKind!
577        }
578
579        type Query {
580          pet: PetModelKind
581        }
582        """
583        )
584
585
586@with_local_registry
587def test_django_objecttype_name_connection_propagation():
588    class Reporter(DjangoObjectType):
589        class Meta:
590            model = ReporterModel
591            name = "CustomReporterName"
592            filter_fields = ["email"]
593            interfaces = (Node,)
594
595    class Query(ObjectType):
596        reporter = Node.Field(Reporter)
597        reporters = DjangoFilterConnectionField(Reporter)
598
599    assert Reporter._meta.name == "CustomReporterName"
600    schema = str(Schema(query=Query))
601
602    assert "type CustomReporterName implements Node {" in schema
603    assert "type CustomReporterNameConnection {" in schema
604    assert "type CustomReporterNameEdge {" in schema
605
606    assert "type Reporter implements Node {" not in schema
607    assert "type ReporterConnection {" not in schema
608    assert "type ReporterEdge {" not in schema
609