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